跳到主要内容

类型转换

问题

Rust 有哪些类型转换方式?From/Into/AsRef/Deref 各有什么用?

答案

转换方式总览

方式开销可失败典型用法
as零成本否(可截断)数值类型转换
From/Into可能分配值转换
TryFrom/TryInto可能分配可能失败的转换
AsRef/AsMut零成本引用转换
Deref/DerefMut零成本智能指针自动解引用
Borrow/ToOwned可能分配借用/拥有对偶

From 与 Into

// From:定义如何从另一个类型创建
impl From<&str> for String {
fn from(s: &str) -> Self { s.to_string() }
}

// 实现 From 自动获得 Into
let s: String = String::from("hello");
let s: String = "hello".into(); // 自动获得

// 自定义 From
struct Email(String);

impl From<String> for Email {
fn from(s: String) -> Self {
Email(s)
}
}

impl From<&str> for Email {
fn from(s: &str) -> Self {
Email(s.to_string())
}
}

let email: Email = "user@example.com".into();
实现 From 还是 Into?

永远实现 From,不要手动实现 Into。实现 From<T> for U 会自动获得 Into<U> for T

TryFrom 与 TryInto

use std::convert::TryFrom;

struct Port(u16);

impl TryFrom<i32> for Port {
type Error = String;

fn try_from(value: i32) -> Result<Self, Self::Error> {
if value >= 0 && value <= 65535 {
Ok(Port(value as u16))
} else {
Err(format!("端口号 {} 超出范围", value))
}
}
}

let port = Port::try_from(8080)?; // Ok(Port(8080))
let port = Port::try_from(-1)?; // Err("端口号 -1 超出范围")

AsRef 与 AsMut

零成本引用转换,用于泛型函数接受多种引用类型:

// AsRef 让函数同时接受 &str、String、&String 等
fn print_path(path: impl AsRef<std::path::Path>) {
println!("{}", path.as_ref().display());
}

print_path("/tmp/file"); // &str
print_path(String::from("/tmp")); // String
print_path(std::path::Path::new("/tmp")); // &Path

Deref 与 DerefMut

智能指针的自动解引用

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}

let x = MyBox(String::from("hello"));
// Deref 强制转换链:&MyBox<String> → &String → &str
fn greet(name: &str) { println!("Hello, {}!", name); }
greet(&x); // 自动 Deref 转换

标准库的 Deref 链:

  • &Box<T>&T
  • &String&str
  • &Vec<T>&[T]
  • &Arc<T>&T

Borrow 与 ToOwned

use std::borrow::Borrow;

// Borrow:HashMap 的 key 查找
fn find_user(map: &HashMap<String, User>, name: &str) -> Option<&User> {
map.get(name) // String 实现了 Borrow<str>
}

// ToOwned:从借用创建拥有的版本
let s: &str = "hello";
let owned: String = s.to_owned(); // &str → String

let slice: &[i32] = &[1, 2, 3];
let owned: Vec<i32> = slice.to_owned(); // &[i32] → Vec<i32>

常见面试问题

Q1: as 转换有哪些陷阱?

答案

as 是底层转换,不检查溢出:

let x: i32 = 300;
let y: u8 = x as u8; // 截断为 44(300 % 256)

let f: f64 = 1e20;
let n: i32 = f as i32; // 饱和为 i32::MAX

let p: *const i32 = &42;
let addr: usize = p as usize; // 指针转地址

建议:数值转换优先用 TryFrom/TryInto,只在明确无截断风险时用 as

Q2: AsRefBorrow 有什么区别?

答案

  • AsRef<T>:纯粹的引用转换,无额外语义约束
  • Borrow<T>:要求借用形式与原始形式有相同的 HashEqOrd 行为

HashMap<K, V>get() 方法要求 K: Borrow<Q>,因为它需要保证 k.borrow() 的哈希值与 k 本身一致。

Q3: 为什么 String 可以当 &str 用?

答案

因为 String 实现了 Deref<Target = str>。Rust 的 Deref 强制转换会在需要 &str 的地方自动将 &String 转换为 &str

这个机制也适用于函数参数、方法调用、比较操作等场景。

Q4: 我的自定义类型应该实现哪些转换 Trait?

答案

  • 有自然的从 A→B 无损转换 → 实现 From<A> for B
  • 转换可能失败 → 实现 TryFrom<A> for B
  • 需要作为函数参数接受多种类型 → 实现 AsRef<T>
  • 是智能指针/包装类型 → 实现 Deref<Target = Inner>
  • 需要支持 HashMap key 查找 → 实现 Borrow<T>

相关链接