TryFrom / TryInto ------ 可失败的转换
一、这些 Trait 定义了数据的什么行为
rust
pub trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
TryFrom 的含义:"我从 T 转换而来------但可能失败。"
与 From 的区别:
From<T> |
TryFrom<T> |
|
|---|---|---|
| 结果 | 总是成功(Self) |
可能成功/失败(Result<Self, Error>) |
| 用途 | 安全的、不会失败的转换 | 可能失败的转换 |
| 例子 | i32 → i64 |
i64 → i32(可能溢出) |
| 实现关系 | From 的实现者应该保证不失败 |
失败是预期行为 |
二、C 中是什么
2.1 C 的"可失败转换"
c
// C 中有两种方式表示"转换失败":
// 方式 1:返回特殊值
#include <stdlib.h>
int n = atoi("42"); // ✅ 成功:n = 42
int m = atoi("hello"); // ❌ 失败:返回 0!但 "0" 也是合法输入!
// atoi 无法区分"转换失败"和"转换结果是 0"
// 方式 2:使用 errno
#include <stdlib.h>
#include <errno.h>
errno = 0;
long n = strtol("hello", NULL, 10);
if (errno != 0) {
// 转换失败
}
// 但:必须事先清空 errno
// errno 是全局状态------线程不安全(除非用线程局部存储)
// 调用者容易忘记检查 errno
// 方式 3:检查指针参数
char* end;
long n = strtol("42abc", &end, 10);
if (*end != '\0') {
// 不是完全有效的数字------只转换了 "42"
}
// 复杂:需要三级检查(errno、endptr、返回值)
2.2 C 的整数截断
c
// C 中大类型转小类型------静默截断
int64_t big = 0x1_0000_0000; // 4294967296
int32_t small = (int32_t)big; // ❌ 0------静默截断!
// 编译器最多给一个 warning,但代码编译通过
三、C 的问题
3.1 转换失败没有类型安全的表达
c
// atoi 的返回值是 int------成功和失败用同一个类型表达
// 失败返回 0,但 0 也是合法的转换结果
// 调用者无法区分这两种情况
int val = atoi("0"); // 转换成功:val = 0
int val = atoi("abc"); // 转换失败:val = 0
// 哪个是哪个?------无法区分
3.2 errno 的不安全性
c
errno = 0;
long val = strtol("abc", NULL, 10);
if (errno != 0) { /* 失败 */ }
// 但如果有另一个系统调用在这之间被调用了呢?
errno = 0;
long val = strtol("abc", NULL, 10);
printf("val = %ld\n", val); // printf 可能会改变 errno!
if (errno != 0) { /* 现在检查已经晚了 */ }
3.3 隐式截断
c
// C 的类型转换可能静默丢失数据
int64_t big = 3000000000; // 超过 32 位 int 范围
int32_t small = big; // ❌ 隐式截断------结果不知是多少
// 有些编译器给 warning,但代码还是编译了
四、Rust 为什么需要这些 Trait
4.1 TryFrom 的例子
rust
use std::convert::TryFrom;
// i64 → i32:可能失败(有符号溢出)
let x: i64 = 3000000000;
match i32::try_from(x) {
Ok(val) => println!("{}", val),
Err(e) => println!("溢出: {}", e), // ✅
}
// 安全的:i32 → i64 总是成功------对应 From
let y: i64 = i64::from(42i32); // ✅ From 就够了
// 另一个例子:字符串中的字符提取
let chars = "hello".chars().collect::<Vec<_>>();
let first = chars[0]; // char
// char → String:可能失败(UTF-8 编码问题?不------char 总是合法 UTF-8)
4.2 标准库中的 TryFrom
rust
// 数值截断------所有大类型到小类型
i32::try_from(i64::MAX); // Err(OutOfRange)
i32::try_from(42i64); // Ok(42)
// 数组转换
let arr: [u8; 4] = [0, 0, 0, 42];
let n = u32::try_from(arr); // ✅ 从 [u8; 4] 到 u32
// IP 地址
let ip = "192.168.1.1".parse::<IpAddr>(); // 通过 FromStr(类似 TryFrom)
4.3 与 C 的对比
c
// C ------ 失败信息在 errno 中,易丢失
errno = 0;
long val = strtol("abc", NULL, 10);
// val = 0,errno = EINVAL
// 如果忘记检查 errno------错误被吞了
rust
// Rust ------ 失败在类型中,必须处理
let val: Result<i64, ParseIntError> = "abc".parse();
// val 是 Result------你必须 match 或 unwrap
// 不能"忘记"检查
4.4 泛型中的 TryFrom
rust
// 泛型函数:尝试从 &str 解析出 T
fn parse_field<T: TryFrom<&str>>(input: &str) -> Result<T, T::Error> {
T::try_from(input)
}
// 可以对任何实现了 TryFrom<&str> 的类型使用
let x: i32 = parse_field("42")?; // ✅
let y: f64 = parse_field("3.14")?; // ✅
// let z: String = parse_field("hello")?; // ❌ String 没有 TryFrom<&str>
4.5 自定义 TryFrom
rust
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err("不是偶数")
}
}
}
let ok = EvenNumber::try_from(42); // Ok(EvenNumber(42))
let err = EvenNumber::try_from(43); // Err("不是偶数")
五、对比
| 维度 | C | Rust TryFrom / TryInto |
|---|---|---|
| 失败表达 | 特殊返回值 / errno |
Result<T, Error>------类型安全 |
| 是否强制检查 | 不强制(容易忘记) | 强制------Result 必须处理 |
| 线程安全 | errno 可能线程不安全 |
线程安全 |
| 错误信息 | strerror(errno) |
自定义 Error 类型 |
| 泛型支持 | 无 | T: TryFrom<U> |
一句话总结 :C 中"可失败转换"通过特殊返回值(
atoi返回 0 表示失败)或全局errno来表达------前者无法区分"失败"和"合法值",后者线程不安全且容易忘记检查。Rust 的TryFrom/TryInto用Result<T, Error>统一表达可失败转换------转换成功时携带值,失败时携带错误类型,编译器强制你处理这两种可能。