核心番外-rust 可变性与所有权、借用的关系

可变性、所有权和借用

在 Rust 中,可变性(mutability)、所有权(ownership)和借用(borrowing)是三个核心概念,它们在一起决定了如何在内存中管理数据、避免数据竞争以及确保程序的安全性。这些概念互相联系,共同维护了 Rust 的内存安全性。

让我们逐一展开这些概念,并了解它们之间的关系。


1. 所有权(Ownership)

所有权是 Rust 的一个核心概念,它描述了每个值在程序中的拥有者。每个值在程序中只能有一个所有者,所有权在赋值、传参等操作时可以发生转移。所有权有以下规则:

  1. 每个值都有一个所有者(一个变量)。
  2. 每个值只能有一个所有者,在同一时间内不能有两个变量指向同一个值。
  3. 当所有者超出作用域时,值会被销毁,内存自动释放(没有垃圾回收机制)。

示例:

fn main() {
let s1 = String::from("Hello"); // `s1` 拥有 String 的所有权
let s2 = s1; // `s2` 现在拥有所有权,`s1` 不再有效
// println!("{}", s1); // 错误:`s1` 已经不再有效
println!("{}", s2); // 输出:Hello
}

在上面的例子中,s1 将所有权转移给了 s2,因此 s1 不再有效。


2. 借用(Borrowing)

借用指的是让某个变量在不转移所有权的情况下被另一个变量使用。借用分为两种类型:不可变借用可变借用

(1) 不可变借用(Immutable Borrowing)

允许多个地方同时读取同一个值,但不允许修改该值。

示例:

fn main() {
let s = String::from("Hello");
let s_ref1 = &s; // 不可变借用
let s_ref2 = &s; // 另一个不可变借用
println!("{}", s_ref1); // 输出:Hello
println!("{}", s_ref2); // 输出:Hello
}

在这个例子中,s_ref1s_ref2s 的不可变借用,它们都可以访问 s 的数据,但不能修改它。

(2) 可变借用(Mutable Borrowing)

允许一个地方修改借用的值,但在借用期间不能再有其他地方同时访问该值。

示例:

fn main() {
let mut s = String::from("Hello");
let s_ref_mut = &mut s; // 可变借用
s_ref_mut.push_str(", world!"); // 修改 `s`
println!("{}", s_ref_mut); // 输出:Hello, world!
}

这里 s_ref_muts 的可变借用,它可以修改 s 的值。注意,在存在可变借用的期间,不能再有任何不可变借用或其他可变借用。

借用规则

  • 不可变借用允许多个不可变引用,但不允许可变借用或修改。
  • 可变借用时,不能有任何不可变或其他可变借用。

3. 可变性(Mutability)

Rust 中的可变性分为变量可变性数据可变性

(1) 变量可变性

如果你想要修改一个变量绑定的值,需要使用 mut 关键字。默认情况下,Rust 中的变量是不可变的。

示例:

fn main() {
let mut x = 5; // `x` 变量是可变的
println!("{}", x); // 输出:5
x = 10; // 修改 `x`
println!("{}", x); // 输出:10
}

在这个例子中,x 是可变的,因此可以在后续修改它的值。

(2) 数据可变性

对于一个值的可变性,可以通过 mut 来决定数据本身是否可变。例如,对于一个 Vec,你需要使用 mut 来表示该数据结构可变。

示例:

fn main() {
let mut vec = vec![1, 2, 3];
vec.push(4); // 修改 vec,添加新元素
println!("{:?}", vec); // 输出:[1, 2, 3, 4]
}

此时,vec 是可变的,因此可以对其进行修改。


4. 所有权、借用和可变性的关系

这三者密切相关,共同决定了 Rust 如何管理内存和避免数据竞争。我们总结它们之间的关系:

  • 所有权:每个值只能有一个所有者,所有权的转移导致值的生命周期结束。
  • 借用:借用允许多个地方使用数据,但不转移所有权。借用有不可变借用和可变借用之分:
    • 不可变借用:允许多个地方读取数据,但不能修改。
    • 可变借用:允许修改数据,但在借用期间不能有其他的借用(不可变或可变)。
  • 可变性:如果你需要修改一个变量或数据,你必须声明其为可变的(mut)。

关键点

  1. 在有可变借用时,不能同时存在不可变借用。
  2. 如果一个变量是可变的,那么它的所有权可以被修改。
  3. 借用不会转移所有权,借用的生命周期必须小于原所有者的生命周期。

5. 综合示例

fn main() {
let mut s = String::from("Hello");
let s_ref1 = &s; // 不可变借用
let s_ref2 = &s; // 另一个不可变借用
println!("{}", s_ref1); // 输出:Hello
println!("{}", s_ref2); // 输出:Hello
let s_ref_mut = &mut s; // 可变借用(这会引发编译错误,因为有不可变借用)
s_ref_mut.push_str(", world!"); // 编译错误
}

上面的例子会报错,因为我们在存在不可变借用的情况下尝试创建可变借用。Rust 编译器会阻止这种潜在的数据竞争和内存问题。


总结

  1. 所有权:每个值只能有一个所有者,所有权会在转移时改变,且在所有者生命周期结束时自动清理内存。
  2. 借用:借用允许数据的共享访问(不可变借用)或修改(可变借用),而不转移所有权。
  3. 可变性mut 关键字允许修改变量或数据的值,但借用的规则会对可变性产生影响。

值传递和引用借用

在 Rust 中,所有权系统确实以 按值传递引用借用 为核心,这两个是最基本的所有权传递方式。但还有一些重要的补充概念需要注意,包括 可变借用智能指针

Rust 中的核心所有权传递方式

  1. 按值传递

    • 默认情况下,函数参数会移动(转移)所有权。调用函数后,原始变量不可再使用。
  2. 不可变借用(&T

    • 借用数据的只读访问权,不能修改被借用的数据。
  3. 可变借用(&mut T

    • 借用数据的读写访问权,但一个时间点只允许一个可变借用。
  4. 智能指针(例如 Box<T>Rc<T>Arc<T>

    • 用于复杂的所有权管理场景,比如堆分配数据的共享和引用计数。

关于 *T 的使用

Rust 中是支持 *T 的用法,但这与原生指针和解引用操作相关。以下是关键点:

  1. *T 代表裸指针(Raw Pointer)

    • 裸指针(如 *const T*mut T)用于底层编程或与 C 接口交互。裸指针不受 Rust 所有权规则约束,但使用时需要极其小心,因为它可能导致未定义行为。
    • 例如:
      let x = 10;
      let ptr: *const i32 = &x; // 裸指针
      unsafe {
      println!("{}", *ptr); // 解引用裸指针需要 `unsafe` 块
      }
  2. *T 作为解引用操作符

    • 在 Rust 中,解引用操作符用于访问指针(引用或智能指针)指向的数据。
    • 解引用引用:
      let x = 42;
      let y = &x;
      assert_eq!(x, *y); // 解引用 `y` 得到 `x` 的值
    • 解引用智能指针:
      let boxed = Box::new(42); // 堆分配
      assert_eq!(42, *boxed); // 解引用智能指针
  3. *T 和所有权系统的关系

    • *T 不常直接使用,因为 Rust 的所有权和引用规则更适合常见场景。
    • 除非需要底层操作或与 C/C++ 交互,否则推荐使用安全抽象(&TBox<T> 等)。

总结

  • Rust 的核心所有权模型:默认按值传递,通过 &T&mut T 借用数据,函数定义和调用需要明确匹配。
  • *T 是裸指针:主要用于底层编程或特殊需求,不推荐直接用于日常开发。
  • Rust 强烈鼓励使用安全的引用类型(&TBox<T> 等),通过所有权系统和类型检查提供内存安全性。

虽然 Rust 借鉴了一些语言(如 C++),但它抛弃了裸指针的广泛使用,并用引用和智能指针代替,提供了更安全的所有权模型。

posted @   代码世界faq  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示