Type Conversions in Rust
Rust 中的类型转换
What you’ll learn:
FromandIntofor zero-cost conversions,TryFromfor fallible conversions, howimpl From<A> for Bautomatically enablesInto<B> for A, and common string conversion patterns.
本章将学习:From和Into如何表达零成本类型转换,TryFrom如何处理可能失败的转换,为什么实现impl From<A> for B会自动得到Into<B> for A,以及常见的字符串转换写法。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
Python usually handles conversion through constructors such as int("42")、str(42)、float("3.14")。Rust 则把类型转换抽象成 From 和 Into trait,用类型系统把可转换关系表达清楚。
Python 习惯通过构造函数完成转换,例如 int("42")、str(42)、float("3.14")。Rust 则把转换能力放进 From 和 Into trait 里,让可转换关系成为类型系统的一部分。
Python Type Conversion
Python 的类型转换
# Python — explicit constructors for conversion
x = int("42") # str → int (can raise ValueError)
s = str(42) # int → str
f = float("3.14") # str → float
lst = list((1, 2, 3)) # tuple → list
# Custom conversion via __init__ or class methods
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°C
Rust From/Into
Rust 的 From / Into
#![allow(unused)]
fn main() {
// Rust — From trait defines conversions
// Implementing From<T> gives you Into<U> automatically!
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)
}
}
// Now both work:
let c1 = Celsius::from(Fahrenheit(212.0)); // Explicit From
let c2: Celsius = Fahrenheit(212.0).into(); // Into (automatically derived)
// String conversions:
let s: String = String::from("hello"); // &str → String
let s: String = "hello".to_string(); // Same thing
let s: String = "hello".into(); // Also works (From is implemented)
let num: i64 = 42i32.into(); // i32 → i64 (lossless, so From exists)
// let small: i32 = 42i64.into(); // ❌ i64 → i32 might lose data — no From
// For fallible conversions, use TryFrom:
let n: Result<i32, _> = "42".parse(); // str → i32 (might fail)
let n: i32 = "42".parse().unwrap(); // Panic if not a number
let n: i32 = "42".parse()?; // Propagate error with ?
}
From is where conversion logic lives. Into is the caller-facing convenience that appears automatically once From is implemented.
真正承载转换逻辑的是 From。而 Into 更像调用侧的便捷接口,只要 From 实现好了,它通常就会自动跟着出现。
The From/Into Relationship
From 和 Into 的关系
flowchart LR
A["impl From<A> for B<br/>为 B 实现 From<A>"] -->|"auto-generates<br/>自动生成"| B["impl Into<B> for A<br/>为 A 自动具备 Into<B>"]
C["Celsius::from(Fahrenheit(212.0))"] ---|"same as<br/>等价于"| D["Fahrenheit(212.0).into()"]
style A fill:#d4edda
style B fill:#d4edda
Rule of thumb: implement
From, notInto. OnceFrom<A> for Bexists, callers automatically getInto<B> for A.
经验法则: 优先实现From,几乎不要手写Into。只要有了From<A> for B,调用方自然就拥有Into<B> for A。
When to Use From/Into
什么时候适合用 From / Into
#![allow(unused)]
fn main() {
// Implement From<T> for your types to enable ergonomic API design:
#[derive(Debug)]
struct UserId(i64);
impl From<i64> for UserId {
fn from(id: i64) -> Self {
UserId(id)
}
}
// Now functions can accept anything convertible to UserId:
fn find_user(id: impl Into<UserId>) -> Option<String> {
let user_id = id.into();
// ... lookup logic
Some(format!("User #{:?}", user_id))
}
find_user(42i64); // ✅ i64 auto-converts to UserId
find_user(UserId(42)); // ✅ UserId stays as-is
}
This pattern makes APIs flexible without losing type safety. The function asks for “anything that can become UserId”, not for one concrete source type.
这种写法能在保持类型安全的前提下,把 API 做得更顺手。函数要的不是某一种死板的输入,而是“任何能转换成 UserId 的东西”。
TryFrom — Fallible Conversions
TryFrom:可能失败的转换
Not every conversion can succeed. Python typically raises exceptions in these cases. Rust uses TryFrom, which returns Result.
并不是所有转换都能保证成功。Python 通常在失败时抛异常,Rust 则用 TryFrom 把这件事建模成返回 Result。
# Python — fallible conversions raise exceptions
try:
port = int("not_a_number") # ValueError
except ValueError as e:
print(f"Invalid: {e}")
# Custom validation in __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) # ValueError at runtime
except ValueError:
pass
#![allow(unused)]
fn main() {
use std::num::ParseIntError;
// TryFrom for built-in types
let n: Result<i32, ParseIntError> = "42".try_into(); // Ok(42)
let n: Result<i32, ParseIntError> = "bad".try_into(); // Err(...)
// Custom TryFrom for validation
#[derive(Debug)]
struct Port(u16);
#[derive(Debug)]
enum PortError {
OutOfRange(u16),
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)),
// Note: u16 max is 65535, so this covers all cases
}
}
}
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"),
PortError::OutOfRange(v) => write!(f, "port {v} out of range"),
}
}
}
// Usage:
let p: Result<Port, _> = 8080u16.try_into(); // Ok(Port(8080))
let p: Result<Port, _> = 0u16.try_into(); // Err(PortError::Zero)
}
Python → Rust mental model:
TryFromis similar to a validating constructor, but instead of throwing an exception it returnsResult, so callers must deal with the failure case explicitly.
Python → Rust 的思维转换:TryFrom很像“带校验的构造函数”,只是它不会抛异常,而是返回Result,于是调用方必须显式处理失败分支。
String Conversion Patterns
字符串转换模式
Strings are one of the most common conversion pain points for Python developers moving to Rust.
对从 Python 转到 Rust 的人来说,字符串转换往往是最容易绕晕的一块。
#![allow(unused)]
fn main() {
// String → &str (borrowing, free)
let s = String::from("hello");
let r: &str = &s; // Automatic Deref coercion
let r: &str = s.as_str(); // Explicit
// &str → String (allocating, costs memory)
let r: &str = "hello";
let s1 = String::from(r); // From trait
let s2 = r.to_string(); // ToString trait (via Display)
let s3: String = r.into(); // Into trait
// Number → String
let s = 42.to_string(); // "42" — like Python's str(42)
let s = format!("{:.2}", 3.14); // "3.14" — like Python's f"{3.14:.2f}"
// String → Number
let n: i32 = "42".parse().unwrap(); // like Python's int("42")
let f: f64 = "3.14".parse().unwrap(); // like Python's float("3.14")
// Custom types → String (implement 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) — like Python's __str__
let s = p.to_string(); // Also works! Display gives you ToString for free.
}
Conversion Quick Reference
转换速查表
| Python | Rust | Notes 说明 |
|---|---|---|
str(x) | x.to_string() | Requires Display impl需要实现 Display |
int("42") | "42".parse::<i32>() | Returns Result返回 Result |
float("3.14") | "3.14".parse::<f64>() | Returns Result返回 Result |
list(iter) | iter.collect::<Vec<_>>() | Type annotation needed 通常需要类型标注 |
dict(pairs) | pairs.collect::<HashMap<_,_>>() | Type annotation needed 通常需要类型标注 |
bool(x) | No direct equivalent 没有完全对应物 | Use explicit checks 改成显式判断 |
MyClass(x) | MyClass::from(x) | Implement From<T>实现 From<T> |
MyClass(x) (validates) | MyClass::try_from(x)? | Implement TryFrom<T>实现 TryFrom<T> |
Conversion Chains and Error Handling
转换链与错误处理
Real applications often chain several conversions together. The contrast with Python becomes very obvious at that point.
真实代码往往会把多次转换串起来写,这时候 Rust 和 Python 的差别会一下子变得特别明显。
# Python — chain of conversions with 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 e
fn 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}"),
}
}
Key insight: every
?is a visible exit point. In Python, any line insidetrymay throw; in Rust, only lines ending with?can short-circuit in that way.
关键理解: 每一个?都是一个肉眼可见的退出点。Python 的try块里几乎每一行都可能抛异常,而 Rust 会把这些可能提前返回的位置明确写在代码表面。📌 See also: Ch. 9 — Error Handling covers
Result、?和自定义错误类型的更多细节。
📌 延伸阅读: 第 9 章——错误处理 会继续深入Result、?与自定义错误类型。
Exercises
练习
🏋️ Exercise: Temperature Conversion Library
🏋️ 练习:温度转换库
Challenge: Build a small temperature conversion library.
挑战:做一个小型温度转换库。
- Define
Celsius(f64)、Fahrenheit(f64)、Kelvin(f64)structs.
1. 定义Celsius(f64)、Fahrenheit(f64)、Kelvin(f64)这三个结构体。 - Implement
From<Celsius> for FahrenheitandFrom<Celsius> for Kelvin.
2. 实现From<Celsius> for Fahrenheit和From<Celsius> for Kelvin。 - Implement
TryFrom<f64> for Kelvinand reject values below absolute zero.
3. 实现TryFrom<f64> for Kelvin,拒绝低于绝对零度的值。 - Implement
Displayfor all three types, such as"100.00°C".
4. 为三个类型都实现Display,输出像"100.00°C"这样的格式。
🔑 Solution
🔑 参考答案
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}"),
}
}
Key takeaway: From handles infallible conversions, while TryFrom handles the ones that may fail. Python often folds both into __init__, but Rust makes the distinction explicit in the type system.
核心收获:From 负责一定成功的转换,TryFrom 负责可能失败的转换。Python 常把这两类事情都塞进 __init__,Rust 则明确把它们区分进类型系统。