3.2 移动语义与复制语义

在 Rust 中,当一个值被赋给另一个变量或作为参数传递给函数时,其所有权可能被移动(move)或复制(copy)。这一行为取决于该值的类型是否实现了 Copy trait。

移动语义(Move)

对于存储在堆上的复杂类型(如 StringVec<T> 等),Rust 默认采用移动语义。这意味着当变量被赋值给另一个变量时,所有权会从原变量转移到新变量,原变量将不再有效。

例如:

let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 移动到 s2

// println!("{}", s1); // 编译错误:s1 已失效
println!("{}", s2); // 正常工作

这种设计防止了多个所有者同时持有同一块堆内存,从而避免了双重释放(double free)等内存安全问题。移动操作本身不涉及堆数据的复制,仅转移栈上的指针、长度和容量信息,因此开销极小。

复制语义(Copy)

对于简单的、存储在栈上的类型(如整数、浮点数、布尔值、字符等),Rust 允许它们在赋值时被复制而非移动。这些类型都实现了 Copy trait。

let x = 5;
let y = x; // x 被复制,x 和 y 都有效

println!("x = {}, y = {}", x, y); // 正常输出

由于栈上数据的复制成本很低,且不会引发内存安全问题,Rust 默认对这类类型启用复制语义。

哪些类型实现了 Copy?

一般来说,以下类型是 Copy 的:

  • 所有整数类型(i32u8 等)
  • 布尔类型(bool
  • 浮点类型(f32f64
  • 字符类型(char
  • 元组,但仅当其所有元素都是 Copy 类型时(如 (i32, bool)Copy,而 (i32, String) 不是)

你不能为包含堆分配数据(如 StringVec<T>)的类型实现 Copy,因为这可能导致浅拷贝带来的悬垂指针或双重释放问题。

函数调用中的移动与复制

当将变量传入函数时,同样遵循移动或复制规则:

fn takes_ownership(s: String) {
    println!("{}", s);
} // s 离开作用域并被 drop

fn makes_copy(x: i32) {
    println!("{}", x);
} // x 是 Copy 类型,离开作用域无影响

let s = String::from("hello");
takes_ownership(s); // s 被移动,之后不可用

let x = 5;
makes_copy(x); // x 被复制,之后仍可用
println!("{}", x);

小结

Rust 通过区分移动和复制语义,在保证内存安全的同时兼顾性能。移动适用于堆分配的复杂类型,防止资源竞争;复制适用于轻量级栈类型,提供便利性。理解何时发生移动、何时发生复制,有助于编写符合所有权规则的正确代码,并减少不必要的克隆操作。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考