06 常用的重要特征Trait
6.1 拷贝控制和移动语义: Clone VS Copy
在Rust中将一个变量赋值给另外一个变量,或者传入一个参数, 其行为和变量是否实现Copy特征有关系; 如果数据没有实现Copy特征Trait,数据的所有权将被转移到新的变量或者到函数入参之中,原来的变量将失效; 如果数据实现了Copy特征Trait,数据的内容将完全根据底层字节memroy copy拷贝到新的变量或者函数临时入参之中,原来的变量继续有效; 常见的基础数据: 整数,浮点,布尔已经内置实现了Copy特征;
Copy特征继承于Clone特征,如果要实现Copy特征,就必须要实现Clone特征; Copy特征没有需要实现的Trait函数,Clone只有一个clone函数需要实现; 在实现Clone特征的clone函数的时候,是对数据进行拷贝产生新的对象,如果存在Copy继承, Clone应该保持逻辑上的一致性; 实现Clone和Copy的方式
// 方式一,通过派生宏,继承编译器默认提供的
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
// 注意:
// 1. 如果继承默认的Copy,则要求所有成员都实现了Copy;
// 2. 如果继承默认的Clone,则要求所有的成员都实现了Clone;
// 3. 如果继承默认的Copy,当前类必须实现Clone特征(无论是继承还是自定义实现);
// 方式二: 自定义clone函数实现Clone特征
#[derive(Debug)]
struct UserSession {
username: String, // 需要被克隆的字段
id: u64, // 需要被克隆的字段
login_token: String, // 假设这个字段在克隆时不需要复制,直接设为空或重新生成
}
// 手动实现 Clone
impl Clone for UserSession {
fn clone(&self) -> Self {
UserSession {
// 正常的字段调用 .clone()
username: self.username.clone(),
id: self.id, // u64 实现了 Copy,可以直接赋值
// 定制化逻辑:克隆时故意不复制原样,而是赋一个新值或默认值
login_token: String::from("new_empty_token"),
}
}
}
6.2 引用自动转换机制: Deref Coercion(解引用强制转换)
当数据的引用 &T, 可以自动转为另外一种的引用 &X 的机制,被称为Deref Coercion(解引用强制转换);
常用于一些数据类型相关联的自动兼容和适配;
比如 &String 能勾自动解引用为 &str;
当T实现了Deref特征之后,Option<T>.as_deref()会自动解析为Option<&targetT>;
下面的代码是Rust内部关于Deref的定义,
是自身引用到另外一个引用的转换,转换目标需要自己定义,
过程一般是内部已有数据的引用:
pub const trait Deref: PointeeSized {
/// The resulting type after dereferencing.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_target"]
#[lang = "deref_target"]
type Target: ?Sized;
/// Dereferences the value.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_method"]
fn deref(&self) -> &Self::Target;
}
备注
在Rust的接口抽象中Trait中定义的类型 type Target: ?Sized;表示长度不确定,需要实现的时候定义;
模式匹配和所有权转移
在 Rust 中,无论是使用 match 还是 if let,**模式匹配是否转移所有权,完全取决于解构出来的数据类型是否实现了 Copy**。
1. 触发所有权转移(Move)的场景
当被匹配的变量**未实现 Copy**(如 String、Vec<T> 或自定义结构体),且你在模式匹配中直接解构出了内部变量名,就会将所有权移动(Move)到该内部变量中,导致原变量失效。
let opt_str: Option<String> = Some(String::from("Rust"));
// 发生所有权转移:String 的所有权被移交给了 inner
match opt_str {
Some(inner) => println!("inner 拿走了所有权: {}", inner),
None => (),
}
// ❌ 编译报错!opt_str 已经被移动,不再有效
// println!("{:?}", opt_str);
2. 避免转移所有权:只匹配引用的 3 种方法
方法一:对匹配表达式直接取引用 &(最常用、最推荐)
直接在 match 或 if let 的目标表达式前加 &。这样匹配出的内部变量会自动推导为引用类型(如 &String),完全不破坏原变量的所有权。
match写法:
let opt_str: Option<String> = Some(String::from("Rust"));
// 对 &opt_str 进行匹配,inner 类型自动推导为 &String
match &opt_str {
Some(inner) => println!("借用查看: {}", inner),
None => (),
}
// ✅ 原变量完全有效
println!("原变量依然可用: {:?}", opt_str);
if let写法:
let opt_str: Option<String> = Some(String::from("Rust"));
if let Some(inner) = &opt_str {
println!("借用查看: {}", inner);
}
// ✅ 原变量完全有效
println!("原变量依然可用: {:?}", opt_str);
方法二:使用 .as_ref() 方法(链式调用时极佳)
.as_ref() 是标准库为 Option<T> 和 Result<T, E> 提供的专属方法。它会返回一个新的包装引用容器(例如将 Option<T> 转为 Option<&T>)。
let opt_str: Option<String> = Some(String::from("Rust"));
// opt_str.as_ref() 生成了 Option<&String>
if let Some(inner) = opt_str.as_ref() {
println!("借用查看: {}", inner);
}
// ✅ 原变量完全有效
println!("原变量依然可用: {:?}", opt_str);
方法三:在模式内部使用 ref 关键字(经典语法)
如果不修饰外部表达式,你可以在模式内部解构变量的前面加上 ref 关键字,显式告诉编译器“我只需要这个字段的不可变引用”。
let opt_str: Option<String> = Some(String::from("Rust"));
match opt_str {
// 明确要求 inner 是 &String 引用
Some(ref inner) => println!("借用查看: {}", inner),
None => (),
}
// ✅ 原变量完全有效
println!("原变量依然可用: {:?}", opt_str);
3. 特殊延伸:如果你需要修改内部数据(可变引用)
当你不仅要避免所有权转移,还需要修改内部的数据时,对应的方法非常简单——只需将“不可变引用”切换为“可变引用”:
需求 |
表达式取引用 |
标准库转换方法 |
模式关键字 |
|---|---|---|---|
只读(不可变引用) |
|
|
|
修改(可变引用) |
|
|
|
修改可变引用示例:
let mut opt_str: Option<String> = Some(String::from("hello"));
// 使用 as_mut() 拿到 Option<&mut String>
if let Some(inner) = opt_str.as_mut() {
inner.push_str(", world!"); // 直接修改底层字符串
}
// ✅ 原变量已被成功修改,输出: Some("hello, world!")
println!("{:?}", opt_str);
总结口诀
基本类型(如
usize、i32、char):已实现Copy,怎么写都不移走所有权。复合类型(如
String、Vec、自定义struct):
直接匹配 $\rightarrow$ 所有权转移(Move)
加
&/.as_ref()/ref$\rightarrow$ 借用引用(Borrow)