类型转换
问题
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: AsRef 和 Borrow 有什么区别?
答案:
AsRef<T>:纯粹的引用转换,无额外语义约束Borrow<T>:要求借用形式与原始形式有相同的Hash、Eq、Ord行为
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> - 需要支持
HashMapkey 查找 → 实现Borrow<T>