From/Into Trait
Rust 中的类型转换
Section titled “Rust 中的类型转换”学习内容: 零成本类型转换的
From和Intotrait、可失败转换的TryFrom、 如何通过impl From<A> for B自动生成Into,以及字符串转换模式。难度: 🟡 中级
Python 用构造函数调用处理类型转换(int("42")、str(42)、float("3.14"))。
Rust 使用 From 和 Into trait 进行类型安全的转换。
Python 类型转换
Section titled “Python 类型转换”# Python — 显式构造函数进行转换x = int("42") # str → int(可能抛出 ValueError)s = str(42) # int → strf = float("3.14") # str → floatlst = list((1, 2, 3)) # tuple → list
# 通过 __init__ 或类方法的自定义转换class Celsius: def __init__(self, temp: float): self.temp = temp
@classmethod def from_fahrenheit(cls, f: float) -> "Celsius": return cls((f - 32.0) * 5.0 / 9.0)
c = Celsius.from_fahrenheit(212.0) # 100.0°CRust From/Into
Section titled “Rust From/Into”// Rust — From trait 定义转换// 实现 From<T> 会自动获得 Into<U>!
struct Celsius(f64);struct Fahrenheit(f64);
impl From<Fahrenheit> for Celsius { fn from(f: Fahrenheit) -> Self { Celsius((f.0 - 32.0) * 5.0 / 9.0) }}
// 现在两种方式都可以:let c1 = Celsius::from(Fahrenheit(212.0)); // 显式 Fromlet c2: Celsius = Fahrenheit(212.0).into(); // Into(自动获得)
// 字符串转换:let s: String = String::from("hello"); // &str → Stringlet s: String = "hello".to_string(); # 同上let s: String = "hello".into(); # 也可以(From 已实现)
let num: i64 = 42i32.into(); # i32 → i64(无损,所以 From 存在)// let small: i32 = 42i64.into(); # i64 → i32 可能丢失数据 — 没有 From
// 对于可失败的转换,使用 TryFrom:let n: Result<i32, _> = "42".parse(); # str → i32(可能失败)let n: i32 = "42".parse().unwrap(); # 不是数字则 paniclet n: i32 = "42".parse()?; # 用 ? 传播错误From/Into 的关系
Section titled “From/Into 的关系”flowchart LR A["impl From<A> for B"] -->|"auto-generates"| B["impl Into<B> for A"] C["Celsius::from(Fahrenheit(212.0))"] ---|"same as"| D["Fahrenheit(212.0).into()"] style A fill:#d4edda style B fill:#d4edda经验法则:总是实现
From,不要直接实现Into。 为A实现From<B>会免费获得Into<A> for B。
何时使用 From/Into
Section titled “何时使用 From/Into”// 为你的类型实现 From<T> 以实现人体工程学的 API 设计:
#[derive(Debug)]struct UserId(i64);
impl From<i64> for UserId { fn from(id: i64) -> Self { UserId(id) }}
// 现在函数可以接受任何可转换为 UserId 的东西:fn find_user(id: impl Into<UserId>) -> Option<String> { let user_id = id.into(); // ... 查找逻辑 Some(format!("User #{:?}", user_id))}
find_user(42i64); // i64 自动转换为 UserIdfind_user(UserId(42)); // UserId 保持原样TryFrom——可失败的转换
Section titled “TryFrom——可失败的转换”并非所有转换都能成功。Python 抛出异常;Rust 使用 TryFrom,返回 Result:
# Python — 可失败的转换抛出异常try: port = int("not_a_number") # ValueErrorexcept ValueError as e: print(f"Invalid: {e}")
# __init__ 中的自定义验证class Port: def __init__(self, value: int): if not (1 <= value <= 65535): raise ValueError(f"Invalid port: {value}") self.value = value
try: p = Port(99999) # 运行时 ValueErrorexcept ValueError: passuse std::num::ParseIntError;
// TryFrom 用于内置类型let n: Result<i32, ParseIntError> = "42".try_into(); // Ok(42)let n: Result<i32, ParseIntError> = "bad".try_into(); // Err(...)
// 自定义 TryFrom 用于验证#[derive(Debug)]struct Port(u16);
#[derive(Debug)]enum PortError { Zero,}
impl TryFrom<u16> for Port { type Error = PortError;
fn try_from(value: u16) -> Result<Self, Self::Error> { match value { 0 => Err(PortError::Zero), 1..=65535 => Ok(Port(value)), } }}
impl std::fmt::Display for PortError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PortError::Zero => write!(f, "port cannot be zero"), } }}
// 使用:let p: Result<Port, _> = 8080u16.try_into(); // Ok(Port(8080))let p: Result<Port, _> = 0u16.try_into(); // Err(PortError::Zero)Python → Rust 心智模型:
TryFrom= 验证并可能失败的__init__。 但不是抛出异常,而是返回Result——调用者必须处理错误情况。
字符串转换模式
Section titled “字符串转换模式”字符串是 Python 开发者最常见的转换困惑来源:
// String → &str(借用,免费)let s = String::from("hello");let r: &str = &s; // 自动 Deref 强制转换let r: &str = s.as_str(); // 显式
// &str → String(分配,有成本)let r: &str = "hello";let s1 = String::from(r); # From traitlet s2 = r.to_string(); # ToString trait(通过 Display)let s3: String = r.into(); # Into trait
// Number → Stringlet s = 42.to_string(); # "42" — 类似 Python 的 str(42)let s = format!("{:.2}", 3.14); # "3.14" — 类似 Python 的 f"{3.14:.2f}"
// String → Numberlet n: i32 = "42".parse().unwrap(); # 类似 Python 的 int("42")let f: f64 = "3.14".parse().unwrap(); # 类似 Python 的 float("3.14")
// 自定义类型 → String(实现 Display)use std::fmt;
struct Point { x: f64, y: f64 }
impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) }}
let p = Point { x: 1.0, y: 2.0 };println!("{p}"); // (1, 2) — 类似 Python 的 __str__let s = p.to_string(); # 也可以!Display 免费给你 ToString。转换快速参考
Section titled “转换快速参考”| Python | Rust | 备注 |
|---|---|---|
str(x) | x.to_string() | 需要实现 Display |
int("42") | "42".parse::<i32>() | 返回 Result |
float("3.14") | "3.14".parse::<f64>() | 返回 Result |
list(iter) | iter.collect::<Vec<_>>() | 需要类型标注 |
dict(pairs) | pairs.collect::<HashMap<_,_>>() | 需要类型标注 |
bool(x) | 无直接等价物 | 使用显式检查 |
MyClass(x) | MyClass::from(x) | 实现 From<T> |
MyClass(x)(验证) | MyClass::try_from(x)? | 实现 TryFrom<T> |
转换链与错误处理
Section titled “转换链与错误处理”现实世界的代码通常链式调用多个转换。对比这些方法:
# Python — 带 try/except 的转换链def parse_config(raw: str) -> tuple[str, int]: try: host, port_str = raw.split(":") port = int(port_str) if not (1 <= port <= 65535): raise ValueError(f"Bad port: {port}") return (host, port) except (ValueError, AttributeError) as e: raise ConfigError(f"Invalid config: {e}") from efn parse_config(raw: &str) -> Result<(String, u16), String> { let (host, port_str) = raw .split_once(':') .ok_or_else(|| "missing ':' separator".to_string())?;
let port: u16 = port_str .parse() .map_err(|e| format!("invalid port: {e}"))?;
if port == 0 { return Err("port cannot be zero".to_string()); }
Ok((host.to_string(), port))}
fn main() { match parse_config("localhost:8080") { Ok((host, port)) => println!("Connecting to {host}:{port}"), Err(e) => eprintln!("Config error: {e}"), }}核心洞见:每个
?都是一个可见的退出点。 在 Python 中,try内的任何一行都可能抛出——在 Rust 中,只有以?结尾的行才可能失败。另请参阅:第 9 章 — 错误处理 深入讲解了
Result、?和使用thiserror的自定义错误类型。
练习:温度转换库(点击展开)
挑战:构建一个微型温度转换库:
- 定义
Celsius(f64)、Fahrenheit(f64)和Kelvin(f64)结构体 - 实现
From<Celsius> for Fahrenheit和From<Celsius> for Kelvin - 实现
TryFrom<f64> for Kelvin,拒绝绝对零度以下的值(-273.15°C = 0K) - 为所有三种类型实现
Display(如"100.00°C")
答案
use std::fmt;
struct Celsius(f64);struct Fahrenheit(f64);struct Kelvin(f64);
impl From<Celsius> for Fahrenheit { fn from(c: Celsius) -> Self { Fahrenheit(c.0 * 9.0 / 5.0 + 32.0) }}
impl From<Celsius> for Kelvin { fn from(c: Celsius) -> Self { Kelvin(c.0 + 273.15) }}
#[derive(Debug)]struct BelowAbsoluteZero;
impl fmt::Display for BelowAbsoluteZero { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "temperature below absolute zero") }}
impl TryFrom<f64> for Kelvin { type Error = BelowAbsoluteZero;
fn try_from(value: f64) -> Result<Self, Self::Error> { if value < 0.0 { Err(BelowAbsoluteZero) } else { Ok(Kelvin(value)) } }}
impl fmt::Display for Celsius { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}°C", self.0) } }impl fmt::Display for Fahrenheit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}°F", self.0) } }impl fmt::Display for Kelvin { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}K", self.0) } }
fn main() { let boiling = Celsius(100.0); let f: Fahrenheit = Celsius(100.0).into(); let k: Kelvin = Celsius(100.0).into(); println!("{boiling} = {f} = {k}");
match Kelvin::try_from(-10.0) { Ok(k) => println!("{k}"), Err(e) => println!("Error: {e}"), }}关键收获:From 处理不可能失败的转换(Celsius→Fahrenheit 总是成功)。
TryFrom 处理可能失败的转换(负的 Kelvin 是不可能的)。
Python 在 __init__ 中混淆两者——Rust 在类型系统中明确区分。