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. 特殊延伸:如果你需要修改内部数据(可变引用)

当你不仅要避免所有权转移,还需要修改内部的数据时,对应的方法非常简单——只需将“不可变引用”切换为“可变引用”:

需求

表达式取引用

标准库转换方法

模式关键字

只读(不可变引用)

match &var 或 if let ... = &var

var.as_ref()

Some(ref inner)

修改(可变引用)

match &mut var 或 if let ... = &mut var

var.as_mut()

Some(ref mut inner)

  • 修改可变引用示例:

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);

总结口诀

  1. 基本类型(如 usize、i32、char):已实现 Copy,怎么写都不移走所有权。

  2. 复合类型(如 String、Vec、自定义 struct):

  • 直接匹配 $\rightarrow$ 所有权转移(Move)

  • 加 & / .as_ref() / ref $\rightarrow$ 借用引用(Borrow)