bigywater.tech
Rust · 49

From、Into 与类型转换

From 与 Into 是 Rust 里最常见的类型转换 trait。这篇验证它们的边界:标准库认为哪些转换是「无损」可以用 From、? 运算符怎么借 From 转换错误、写 impl Into 参数能省多少分配,以及为什么泛型代码里 T: Into 总是成立。

E0277
i32::from(i64) 这类有损转换没有 From
1 vs 0
传 &str 与传 String 给 impl Into 参数的堆分配次数
?
遇到 Err 时通过 From 自动转换错误类型

标准库的 From

实测 · 本机执行输出
  源码:
      1 | use std::collections::HashMap;
      2 | fn main() {
      3 |     let a = i64::from(7i32);                // i32 → i64 无损
      4 |     let b = f64::from(3u8);
      5 |     let s = String::from("text"); let c = String::from('c');
      6 |     let v = Vec::from([1, 2, 3]); let bs: Vec<u8> = Vec::from("hi");
      7 |     let m = HashMap::from([("a", 1), ("b", 2)]);
      8 |     println!("{a} {b} {s} {c} {:?} {:?} 长度 {}", v, bs, m.len());
      9 | }
  编译:成功
  运行(退出码 0):
    7 3 text c [1, 2, 3] [104, 105] 长度 2
  源码:
      1 | fn main() { let x: i32 = i32::from(5i64); let y: u8 = u8::from(300u32); }
  编译:失败
    error[E0277]: the trait bound `i32: From<i64>` is not satisfied
     --> <实验目录>/src/u49b.rs:1:26
      |
    1 | fn main() { let x: i32 = i32::from(5i64); let y: u8 = u8::from(300u32); }
      |                          ^^^ the trait `From<i64>` is not implemented for `i32`
      |
      = help: `i32` implements trait `From<T>`:
                From<bool>
                From<i16>
                From<i8>
                From<u16>
                From<u8>
    
    error[E0277]: the trait bound `u8: From<u32>` is not satisfied
        --> <实验目录>/src/u49b.rs:1:55
         |
       1 | fn main() { let x: i32 = i32::from(5i64); let y: u8 = u8::from(300u32); }
         |                                                       ^^ the trait `From<u32>` is not implemented for `u8`
         |
    help: `u8` implements trait `From<T>`
        --> <rust 标准库源码>/library/core/src/convert/num.rs:37:9
         |
      37 |         const impl From<bool> for $int {
         |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<bool>`
    ...
      58 | impl_from_bool!(u8 u16 u32 u64 u128 usize);
    …(共 40 行)

自己的类型与 ?

实测 · 本机执行输出
  源码:
      1 | use std::num::ParseIntError;
      2 | use std::fmt;
      3 | #[derive(Debug)] enum AppError { Parse(ParseIntError), Negative(i32) }
      4 | impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::Parse(e) } }
      5 | impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { AppError::Parse(e) => write!(f, "解析失败:{e}"), AppError::Negative(n) => write!(f, "{n} 是负数") } } }
      6 | fn parse_positive(s: &str) -> Result<i32, AppError> {
      7 |     let n: i32 = s.parse()?;               // ParseIntError 通过 From 自动变成 AppError
      8 |     if n < 0 { return Err(AppError::Negative(n)); }
      9 |     Ok(n)
     10 | }
     11 | fn main() {
     12 |     for s in ["42", "abc", "-5"] { match parse_positive(s) { Ok(n) => println!("{s} → Ok({n})"), Err(e) => println!("{s} → Err({e})") } }
     13 | }
  编译:成功
  运行(退出码 0):
    42 → Ok(42)
    abc → Err(解析失败:invalid digit found in string)
    -5 → Err(-5 是负数)

? 的展开过程见? 运算符展开过程;自定义错误类型见自定义错误类型。

impl Into 参数

实测 · 本机执行输出
  源码:
      1 | use std::alloc::{GlobalAlloc, Layout, System};
      2 | use std::sync::atomic::{AtomicUsize, Ordering};
      3 | struct C; static AL: AtomicUsize = AtomicUsize::new(0);
      4 | unsafe impl GlobalAlloc for C {
      5 |     unsafe fn alloc(&self, l: Layout) -> *mut u8 { AL.fetch_add(1, Ordering::Relaxed); unsafe { System.alloc(l) } }
      6 |     unsafe fn dealloc(&self, p: *mut u8, l: Layout) { unsafe { System.dealloc(p, l) } }
      7 | }
      8 | #[global_allocator] static G: C = C;
      9 | struct User { name: String }
     10 | fn new_user(name: impl Into<String>) -> User { User { name: name.into() } }
     11 | fn main() {
     12 |     let owned = String::from("alice");                       // 这次分配不计入下面的统计
     13 |     let a0 = AL.load(Ordering::Relaxed);
     14 |     let u1 = new_user("bob");                                // &str → String:要分配
     15 |     let a1 = AL.load(Ordering::Relaxed);
     16 |     let u2 = new_user(owned);                                // String → String:Into 是恒等转换,直接移动
     17 |     let a2 = AL.load(Ordering::Relaxed);
     18 |     println!("传 &str:分配 {} 次;传 String:分配 {} 次", a1 - a0, a2 - a1);
     19 |     println!("{} {}", u1.name, u2.name);
     20 | }
  编译:成功
  运行(退出码 0):
    传 &str:分配 1 次;传 String:分配 0 次
    bob alice

有可能失败的转换见TryFrom 与失败转换。

自反性

实测 · 本机执行输出
  源码:
      1 | fn total<T: Into<f64>>(items: Vec<T>) -> f64 { items.into_iter().map(Into::into).sum() }
      2 | fn main() { println!("{} {} {}", total(vec![1u8, 2]), total(vec![1.5f32, 2.5]), total(vec![2.5f64, 0.5])); }
  编译:成功
  运行(退出码 0):
    3 4 3

分步演示

From 与 Into
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。分配次数为 release 构建的单次运行
    • 没有验证:From<&String> for String、From>、Into 的 where 子句写法的差别、From 实现对类型推断的影响、From> for Box<[T]> 的分配行为
    京ICP备2026067502号-1京公网安备11010502063487号