Rust 所有权 简介

文章目录

  • 发现宝藏
  • 1. 所有权基本概念
  • 2. 所有权规则
  • 3. 变量作用域
  • 4. 栈与堆
    • 4.1 栈(Stack)
    • 4.2 堆(Heap)
  • 5. String类型
    • 5.1 String 类型
    • 5.2 String 的内存分配
    • 5.3 所有权与内存管理
    • 5.4 String 与切片
  • 6. 变量与数据交互方式
    • 6.1 移动(Move)
    • 6.2. 克隆(Clone)
  • 7. 所有权与函数
    • 7.1. 传递参数
    • 7.2. 返回值
  • 总结

发现宝藏

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【宝藏入口】。


所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。我们将讲到所有权以及相关功能:借用、slice 以及 Rust 如何在内存中布局数据。

1. 所有权基本概念

所有权(ownership)是Rust的核心特性之一,它确保了内存安全,避免了内存泄漏等问题。每个值在Rust中都有一个所有者,即一个变量。每个值只能有一个所有者,当所有者离开作用域时,这个值将被自动丢弃。

2. 所有权规则

首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:

  • Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
  • 值有且只有一个所有者。
  • 当所有者(变量)离开作用域,这个值将被丢弃。

3. 变量作用域

在Rust中,变量的作用域是指变量在程序中有效的范围。当变量离开作用域时,其所有权的值将被自动清理。例如:

let s = "hello";
{
    let s = String::from("hello");
    // 使用 s
} // 此作用域已结束,s 不再有效

好的,让我们详细探讨 Rust 中的栈(Stack)与堆(Heap)存储,以及如何与所有权机制关联。

4. 栈与堆

在 Rust 中,数据可以存储在两种主要的内存区域:栈(Stack)和堆(Heap)。这两种存储方式各自具有不同的特性和用途。

4.1 栈(Stack)

栈是一种具有后进先出(LIFO)特性的内存结构。在栈上分配内存的过程非常高效,因为栈的内存分配和释放只涉及到栈顶指针的简单移动。

4.1.1 特点

  1. 固定大小:栈上存储的数据必须具有固定大小。在编译时,编译器知道数据的确切大小,因此可以在栈上进行高效的内存管理。
  2. 自动管理:栈上的数据在作用域结束时自动释放。这意味着栈上存储的局部变量会在其作用域结束时立即被销毁,栈指针会自动回退。
  3. 有限大小:栈的大小通常较小,超出栈的大小限制会导致栈溢出错误(Stack Overflow)。

4.1.2 示例

fn main() {
    let x = 42; // 整型数据存储在栈上
    let y = 3.14; // 浮点型数据存储在栈上
    let z = 'a'; // 字符型数据存储在栈上
}

在这个例子中,xyz 都是固定大小的数据,它们会被分配在栈上。

4.2 堆(Heap)

堆是一种具有动态分配特性的内存区域,用于存储大小不固定的数据。堆上的内存分配不像栈那样高效,但它适用于需要动态内存管理的情况。

4.2.1 特点

  1. 动态大小:堆上的数据可以具有动态大小。你可以在运行时分配任意大小的内存,这使得堆非常适合存储不确定大小的数据。
  2. 手动管理:在 Rust 中,堆上的内存管理是自动的,由所有权机制管理。堆上的数据会在所有者超出作用域时自动释放。
  3. 可扩展:与栈相比,堆的大小受限较少,可以分配较大的内存块。

4.2.2 示例

fn main() {
    let s = String::from("Hello"); // 字符串在堆上分配内存
}

在这个例子中,String 是一个在堆上分配内存的动态数据结构。它的内存分配不再是固定的,而是由 String 类型内部的堆分配机制来处理。

5. String类型

以String类型为例,它存储在堆上,可以存储在编译时未知大小的文本。当我们创建一个String类型的变量时,实际上是在堆上分配了一块内存。

5.1 String 类型

在 Rust 中,String 是一个动态字符串类型,它与 &str(字符串切片)不同。String 提供了可变的、可增长的字符串,可以在运行时修改其内容,并支持复杂的字符串操作。与 &str 不同的是,String 的内存分配是在堆上进行的。

5.1.1 String 的结构

String 类型的内部结构包括以下几个部分:

  • 指针(Pointer):指向堆上实际存储字符串数据的位置。
  • 长度(Length):当前字符串的字符数(字节数)。
  • 容量(Capacity):堆上分配的总内存量,以字节为单位,通常比实际长度要大,以支持字符串的增长。

这个结构允许 String 具备动态扩展的能力,能够在需要时增长。

5.2 String 的内存分配

当你创建一个 String 实例时,Rust 会在堆上分配足够的内存来存储字符串数据。以下是 String 内存分配的关键步骤:

5.2.1 创建 String

fn main() {
    let s = String::from("Hello, world!"); // 创建一个新的 String
}
  1. 分配内存:Rust 会在堆上分配一块内存来存储字符串数据。在这个过程中,String 会分配比实际需要的更多的内存,以便在未来的操作中能够容纳更多的字符。这种预分配机制有助于减少频繁的内存分配开销。

  2. 存储数据:字符串数据(例如 "Hello, world!")会被复制到堆上的内存中。此时,String 的指针指向堆上这块内存的起始位置。

  3. 更新元数据String 会维护内部的长度和容量信息。长度是当前存储的字符数,而容量是分配的总字节数。这样,Rust 可以有效管理字符串的增长和缩减。

5.2.2 动态增长

当你向 String 中添加更多字符时,Rust 会根据需要动态调整内存分配:

fn main() {
    let mut s = String::from("Hello");
    s.push_str(", world!"); // 动态增长
}
  1. 检查容量String 首先检查当前容量是否足够容纳新增的字符。如果足够,则直接在现有内存中追加字符。

  2. 重新分配:如果当前容量不足以容纳新数据,String 会重新分配更大的堆内存,通常是原来容量的两倍。然后将旧数据复制到新分配的内存中,更新指针,最后释放旧内存。

5.3 所有权与内存管理

String 的内存管理由 Rust 的所有权系统自动处理,确保了内存的安全性和有效性:

  • 所有权转移:当 String 的所有权转移到另一个变量时,堆上的数据也会随之转移,避免了数据的重复释放或访问无效内存的问题。

    fn main() {
        let s1 = String::from("Hello");
        let s2 = s1; // s2 现在拥有堆上的数据
        // println!("{}", s1); // 错误!s1 不再是有效的所有者
        println!("{}", s2); // 正确!s2 是有效的所有者
    }
    
  • 自动释放:当 String 的所有者超出作用域时,Rust 会自动调用 String 的析构函数,释放堆上的内存。这防止了内存泄漏和资源泄漏。

    fn main() {
        {
            let s = String::from("Hello");
            // 使用 s
        } // s 超出作用域,堆内存被释放
    }
    

5.4 String 与切片

虽然 String 是一个动态可变的字符串,但其内部可以借用不可变的字符串切片 &str。这种方式允许在不拥有数据所有权的情况下安全地访问字符串的一部分:

fn main() {
    let s = String::from("Hello, world!");
    let slice: &str = &s[0..5]; // 切片借用字符串的一部分
    println!("{}", slice); // 输出 "Hello"
}

String 类型在 Rust 中提供了强大的动态字符串操作能力。它通过在堆上分配内存来支持可变长度的字符串,并利用 Rust 的所有权系统自动管理内存。了解 String 的内存分配和管理机制能够帮助你更好地编写高效、安全的 Rust 代码。

6. 变量与数据交互方式

6.1 移动(Move)

当将一个变量赋值给另一个变量时,Rust默认会进行移动操作,而非深拷贝。这意味着,原始变量的所有权会转移给新变量,原始变量将不再有效。

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

6.2. 克隆(Clone)

如果我们需要深拷贝堆上的数据,可以使用clone方法。

 let s1 = String::from("hello");
 let s2 = s1.clone(); // s2 是 s1 的深拷贝

7. 所有权与函数

7.1. 传递参数

将值传递给函数时,所有权会转移。如果函数参数是Copy类型的,则会进行拷贝;否则,会进行移动。

 fn takes_ownership(some_string: String) {
     println!("{}", some_string);
 }
 fn makes_copy(some_integer: i32) {
     println!("{}", some_integer);
 }

7.2. 返回值

函数的返回值也可以转移所有权。通过返回值,我们可以将函数内部创建的值的所有权传递给外部。

 fn gives_ownership() -> String {
     let some_string = String::from("hello");
     some_string
 }
 fn takes_and_gives_back(a_string: String) -> String {
     a_string
 }

总结

所有权是Rust语言的核心特性之一,它为内存管理提供了全新的解决方案。掌握所有权机制,有助于我们编写更安全、高效的Rust代码。虽然所有权概念在初学者看来较为复杂,但只要勤加练习,相信大家都能熟练运用。在后续的学习中,我们将继续探讨Rust的其他高级特性。

你可能感兴趣的:(rust,后端,rust,开发语言)