Skip to content

所有权

所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值在任意时刻有且只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除(调用 drop 并回收内存)
  • 所有权可以转移(move)
  • 赋值/传参/函数返回时,如果类型未实现 Copy,所有权将发生转移
  • 基本数据类型(如整数、布尔、浮点、不可变引用 &T)实现了 Copy,会自动复制,不会转移所有权,设计原因与性能有关

核心特性: Rust 通过所有权机制在编译时保证内存安全,避免了垃圾回收开销和野指针问题

复制 copy 和移动 move

1. 基本数据类型

存储在 stack 上,赋值给其他变量不会转移所有权,因为实现了 Copy

rust
let a = 1;
let b = a;  // 直接进行了复制操作,没有发生所有权转移
println!("{a}, {b}"); // 不会出现错误

基本数据类型自动实现了 Copy,本质也遵循所有权规则,只是编译器自动进行了复制

原因: 复制这些微小数据(基本数据类型)的开销甚至比处理它们的指针引用还要低,因此编译器默认开启自动复制

2. 复杂数据类型

存储在 heap 上,直接赋值给变量会转移所有权

rust
let x = String::from("hello");
let y = x;  // 浅拷贝 + x 失效,x 此时移动所有权到 y;修复: 1.let y = x.clone() 或 2.let y = &x
println!("{x}, {y}"); // 报错: x value borrowed here after move

let y = x 这一步是将 stack 里的指针(ptr)、数据长度(len)、数据总大小(capacity)这些信息复制给 y(浅拷贝),并且将 x 回收,避免最后二次释放同一个指针的数据(二次释放)

本质是在没有垃圾回收器的情况下,实现垃圾回收的一种解决方案,保证内存安全

3. 函数传参

  • 同样遵循前所有权规则,不是复制就是移动或者借用(引用传递)
rust
fn main() {
  let s = String::from("hello");
   takes_ownership(s); // s 的所有权被转移到函数内,函数结束后 s 被释放
   // println!("{s}"); // 报错: s value borrowed here after move
   let x = 5;
   makes_copy(x); // x 是基本数据类型,被复制到函数内,函数结束后 x 仍然有效
   println!("{x}"); // 正常输出
}

fn takes_ownership(some_string: String) {
   println!("{}", some_string);
} // some_string 离开作用域,被释放

fn makes_copy(some_integer: i32) {
   println!("{}", some_integer);
} // some_integer 离开作用域,被释放

4. 函数返回值

  • 同样会发生所有权的转移
rust
fn main() {
   let s1 = gives_ownership(); // s1 从函数获得所有权
   println!("{s1}"); // 正常输出
   let s2 = String::from("hello");
   let s3 = takes_and_gives_back(s2); // s2 的所有权被转移到函数内,函数返回后 s3 获得所有权
   println!("{s3}"); // 正常输出

   fn gives_ownership() -> String {
      let some_string = String::from("hello");
      some_string // 返回 some_string 的所有权
   }

   fn takes_and_gives_back(a_string: String) -> String {
      a_string // 返回 a_string 的所有权
   }
}

克隆 Clone

  • 主要针对 heap 数据的深拷贝(heap 生成 2 份相同的数据,stack 上 2 份不同的指针指向自己的 heap),数据较大时比较消耗资源

克隆字符串

rust
let s1 = String::from("hello");
let s2 = s1.clone();  // 深拷贝,生成两份独立的 heap 数据
println!("{s1}, {s2}"); // 正常输出

引用 Reference: &

是一个语法概念,指的是引用某些值而不获得所有权

  • 本质是创建一个指向原数据的指针
  • 引用本身是一个指针,占用固定大小的内存空间
  • 通过引用访问数据时,编译器会自动解引用,因此无需显式使用 *
  • 变量被引用后,原变量就处于锁定状态,无法再直接使用,需要等引用结束后才能使用

语法:

  • &: 不可变引用,不可修改变量的内容
  • &mut: 可变引用,可以修改变量的内容,同作用域内对同一变量独占引用(不可与其他引用共存)
rust
// 不可变引用示例
let x = String::from("hello");
let y = &x;  // y 是对 x 的引用,x 仍保有所有权
println!("{x}, {y}");  // 正常输出,x 依然有效

// 可变引用示例
let mut s = String::from("hello");
let r1 = &mut s;  // r1 是 s 的可变引用
*r1 = String::from("world"); // 通过 r1 修改 s 的内容
println!("{s}");  // 输出 "world",s 的内容被修改

引用 vs 所有权转移: 引用只是借用,原变量保持所有权;而直接赋值会转移所有权

借用

是使用引用的一种行为,指的是通过引用来访问数据而不获取所有权的过程,当你创建了一个引用,就是在借用数据

借用规则

同作用域、同变量时,只能满足下列条件之一:

  1. 一个可变引用(防止数据竞争)
  2. 无限个不可变引用
rust
let mut x = 5;
let r1 = &x;      // 不可变引用
let r2 = &x;      // 不可变引用(√ 可以多个)
let r3 = &mut x;  // ✗ 错误: 不能同时有可变和不可变引用

引用位置语法

函数定义中,& 永远加在类型前面,而不是变量名前面:

rust
// ✓ 正确: & 加在类型 T 前面
fn execute<T>(item: &T) { ... }

// ✗ 错误: 不能加在变量名前面
// fn execute<T>(&item: T) { ... }

解引用 *

  • 解引用后可以读取到真实的原数据
  • 这是编译器提供的语法糖,背后是通过实现 Deref Trait 来实现的
  • 对不可变引用解引用: *y 获取原数据的不可变访问
  • 对可变引用解引用: *y 可以修改原数据
rust
let x = 5;
let y = &x;
println!("{}", *y);  // 输出 5,解引用获取原数据

let mut a = 10;
let b = &mut a;
*b = 20;  // 解引用并修改原数据
println!("{}", a);  // 输出 20

自动解引用: 在大多数情况下,编译器会自动解引用.例如调用方法时,无需显式使用 *;但在某些场景(如数值比较、打印输出)需要显式解引用

重借用

从已有的可变引用创建新的引用(重借用)也是允许的,但原始变量也必须是 mut 的,也必须遵守借用规则

rust
let mut s = String::from("hello");
let r1 = &mut s;       // 可变引用
let r2 = &mut *r1;     // 重借用 r1 创建 r2,也是可变引用
*r2 = String::from("world"); // 通过 r2 修改 s 的内容
// *r1 = String::from("world123"); // 错误: 遵循借用规则,不能同时有多个可变引用
println!("{s}");  // 输出 "world",s 的内容被修改

这里可以通过非词法作用域生命周期规则来修改代码

悬垂引用 Dangling Reference

指引用指向一个已被释放或已重分配的内存地址

  • Rust 编译器会防止悬垂引用的产生,在编译阶段就捕获这类错误
  • 如果函数返回一个引用,该引用必须指向有效的数据
rust
// ✗ 错误示例: 悬垂引用
fn dangle() -> &String {
   let s = String::from("hello");
   &s  // 错误: s 在函数结束时被释放,返回的引用指向已释放内存
}

// ✓ 修复: 返回所有权而不是引用
fn no_dangle() -> String {
   let s = String::from("hello");
   s  // 转移所有权给调用者
}

编译器保证: Rust 的生命周期检查确保引用总是指向有效的数据,不会产生悬垂引用问题

基于 MIT 协议发布