bigywater.tech
Rust · 50

TryFrom 与失败转换

上一篇的 From 只处理「一定成功」的转换。需要处理失败时用 TryFrom。这篇用对比表验证它与 as 的差别、怎么为自己的类型写带校验的转换、无损转换里那个看起来奇怪的 Infallible,以及切片转定长数组这个常见场景。

44 vs Err
300i32 as u8 悄悄得到 44,u8::try_from(300) 返回 Err
Infallible
无损转换的 TryFrom 错误类型,0 字节,永远不会发生
TryFromSliceError
切片长度与数组长度不符

数值转换

实测 · 本机执行输出
  源码:
      1 | use std::convert::TryFrom;
      2 | fn main() {
      3 |     for v in [200i32, 300, -1, 255, 256] {
      4 |         println!("{:>4}:as u8 = {:>3},u8::try_from = {:?}", v, v as u8, u8::try_from(v));
      5 |     }
      6 |     println!("i32::try_from(3_000_000_000u64) = {:?}", i32::try_from(3_000_000_000u64));
      7 |     println!("u32::try_from(-5i64)             = {:?}", u32::try_from(-5i64));
      8 |     let r: Result<u8, _> = 300i32.try_into();
      9 |     println!("300i32.try_into::<u8>()          = {:?}", r);
     10 | }
  编译:成功
  运行(退出码 0):
     200:as u8 = 200,u8::try_from = Ok(200)
     300:as u8 =  44,u8::try_from = Err(TryFromIntError(PosOverflow))
      -1:as u8 = 255,u8::try_from = Err(TryFromIntError(NegOverflow))
     255:as u8 = 255,u8::try_from = Ok(255)
     256:as u8 =   0,u8::try_from = Err(TryFromIntError(PosOverflow))
    i32::try_from(3_000_000_000u64) = Err(TryFromIntError(PosOverflow))
    u32::try_from(-5i64)             = Err(TryFromIntError(NegOverflow))
    300i32.try_into::<u8>()          = Err(TryFromIntError(PosOverflow))

as 转换的更多规则见基本类型与类型推断。

自己的类型

实测 · 本机执行输出
  源码:
      1 | use std::convert::TryFrom;
      2 | #[derive(Debug)] struct Port(u16);
      3 | #[derive(Debug)] enum PortError { Negative, TooBig(i32), Reserved(u16) }
      4 | impl TryFrom<i32> for Port {
      5 |     type Error = PortError;
      6 |     fn try_from(n: i32) -> Result<Self, PortError> {
      7 |         if n < 0 { return Err(PortError::Negative); }
      8 |         let p = u16::try_from(n).map_err(|_| PortError::TooBig(n))?;
      9 |         if p < 1024 { return Err(PortError::Reserved(p)); }
     10 |         Ok(Port(p))
     11 |     }
     12 | }
     13 | fn main() {
     14 |     for n in [8080, -1, 70000, 80] { println!("{:>6} → {:?}", n, Port::try_from(n)); }
     15 |     let p: Result<Port, _> = 443.try_into(); println!("443.try_into() → {:?}", p);
     16 | }
  编译:成功
  运行(退出码 0):
      8080 → Ok(Port(8080))
        -1 → Err(Negative)
     70000 → Err(TooBig(70000))
        80 → Err(Reserved(80))
    443.try_into() → Err(Reserved(443))

Infallible

实测 · 本机执行输出
  源码:
      1 | use std::convert::{Infallible, TryFrom};
      2 | fn main() {
      3 |     let r: Result<u64, Infallible> = u64::try_from(5u32);
      4 |     println!("{:?}", r);
      5 |     match r { Ok(v) => println!("u32 → u64 的 try_from 一定成功:{v}"), Err(never) => match never {} }
      6 |     println!("size_of::<Infallible>() = {}", std::mem::size_of::<Infallible>());
      7 | }
  编译:成功
  运行(退出码 0):
    Ok(5)
    u32 → u64 的 try_from 一定成功:5
    size_of::<Infallible>() = 0

无损转换的 From 见From、Into 与类型转换。

切片转数组

实测 · 本机执行输出
  源码:
      1 | use std::convert::TryInto;
      2 | fn main() {
      3 |     let bytes = [0x12u8, 0x34, 0x56, 0x78, 0x9a];
      4 |     let a: [u8; 4] = bytes[..4].try_into().unwrap();
      5 |     println!("前 4 个字节 → [u8; 4]:{:02x?},大端 u32 = {:#x}", a, u32::from_be_bytes(a));
      6 |     let bad: Result<[u8; 4], _> = bytes[..].try_into();
      7 |     println!("5 个字节 → [u8; 4]:{:?}", bad);
      8 | }
  编译:成功
  运行(退出码 0):
    前 4 个字节 → [u8; 4]:[12, 34, 56, 78],大端 u32 = 0x12345678
    5 个字节 → [u8; 4]:Err(TryFromSliceError(()))

分步演示

TryFrom
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:TryFrom 与 FromStr(parse)的取舍、TryFrom<&str>、Infallible 与 never 类型 ! 的关系(! 仍不稳定)、num 类 crate 的 NumCast
    京ICP备2026067502号-1京公网安备11010502063487号