DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • rust

    • Rust
    • add
    • 属性(attributes)
    • cargo issue
    • cli
    • build.rs
    • Enums
    • eventEmitter(rust)
    • 格式化输出 std::fmt
    • rust iterator
    • rust 学习计划
    • 生命周期(lifetime)
    • Linked List
    • log
    • macros
    • mem::size_of
    • niche optimization
    • Rust 所有权
    • 模式匹配(pattern matching)
    • module system
    • result & option
    • .rust-analyzer.json
    • rust startup
    • rust-test
    • 可见性(visibility)
    • cargo
    • toml

Rust 所有权

零开销内存回收 的一种高效实现方式

Rust 是一种系统编程语言,其设计目的是确保内存安全并防止数据竞争,而不依赖垃圾回收器。这种内存安全性主要通过所有权系统来实现。


内存回收

硬件内存大小受限,数据泄漏,隐私安全

主流编程语言的内存回收机制对比

  • 静态语言
    • 在编译时对变量类型进行检查和确定的语言
    • c,c++,rust
  • 动态语言
    • 在运行时进行类型检查和确定的语言
    • javascript,python

写法对比

rust

fn main() {
    let x: i32 = 5;
    let y = 5;
    println!("The value of x is: {},y is {}", x,y);
}

javascript

let a = 1;  // double float
console.log(a);

差异对比

特性静态语言动态语言
类型检查时间编译时运行时
类型安全更安全,减少运行时类型错误较灵活,但类型错误可能在运行时出现
性能通常更高效,编译器优化通常较低,运行时类型检查
灵活性较低,需明确声明类型较高,允许在运行时改变类型
代码简洁性需要显式类型声明,代码相对冗长通常更简洁,适合快速开发
开发工具支持更强大的静态分析和重构工具开发工具支持有限,但在快速开发上占优势

回收方式对比

C/C++

  • 内存管理方式: 手动管理(Manual Management)
  • 特点:
    • 程序员通过 malloc 和 free(C)或 new 和 delete(C++)手动分配和释放内存。
    • 没有内置的垃圾回收机制。
  • 优点:
    • 高效且灵活,适用于对性能要求极高的系统级编程。
  • 缺点:
    • 容易出现内存泄漏、悬垂指针和缓冲区溢出等问题,需要非常小心的内存管理。
    // 释放分配的内存
    free(ptr); ptr = NULL; // 将指针设为 NULL,避免悬空指针
    // 动态分配一个数组的内存
    int n = 5;int *arr = (int *)malloc(n * sizeof(int));    

JavaScript

  • 内存管理方式: 垃圾回收(Garbage Collection)
  • 特点:
    • 浏览器和 Node.js 环境中均使用垃圾回收器(如 V8 引擎的垃圾回收器)。
    • 采用标记-清除(Mark-and-Sweep)、标记-压缩(Mark-and-Compact)分代回收 等算法。
  • 优点:
    • 自动内存管理,适合快速开发和运行在多平台上的应用。
  • 缺点:
    • 垃圾回收机制在某些情况下可能导致性能问题,如 UI 线程停顿。

Rust

  • 内存管理方式: 所有权系统(Ownership System)
  • 特点:
    • Rust 使用所有权系统进行内存管理,编译器在编译时通过静态分析来确保内存安全。
    • 每个值都有一个所有者,在任何时候只能有一个有效的所有者。
    • 通过借用(引用)机制来共享数据,同时保证数据竞争和悬垂指针的安全。
  • 优点:
    • 在编译时保证内存安全,没有运行时开销。
    • 避免了数据竞争和悬垂指针。
  • 缺点:
    • 需要程序员理解和遵循所有权和借用规则,学习曲线较陡。

stack & Heap

+--------------------+      +--------------------+
|                    |      |                    |
|  高地址             |      |  高地址             |
|  (High Address)     |      |  (High Address)     |
|                    |      |                    |
|  堆 (Heap)          | <----|  自由增长           |
|                    |      |  (Grows Freely)     |
|     |  |  |
| --- ||--------------------|
|                    |      |                    |
|                    |      |                    |
|  未使用 (Unused)    |      |  栈 (Stack)         |
|                    |      |                    |
|     |  |  |
| --- ||--------------------|
|                    |      |                    |
|  低地址             |      |  低地址             |
|  (Low Address)      |      |  (Low Address)      |
+--------------------+      +--------------------+

为什么要有所有权

double free , memo safety

drop 释放内存

    let x = 5;
    let y = x;

    let s1 = String::from("hello");
    let s2 = s1;

alt text


所有权规则

Rust 是一种系统编程语言,其设计目的是确保内存安全并防止数据竞争,而不依赖垃圾回收器。这种内存安全性主要通过所有权系统来实现.

1. 所有权的基本规则

Rust 的所有权系统有三个基本规则:

  1. 每一个值都有一个所有者(owner)。
  2. 在任一时刻,值只能有一个所有者。
  3. 当所有者离开作用域(scope),值会被丢弃(drop)。

变量的作用域

变量在声明的地方引入作用域,并在离开作用域时释放资源:

{
    let s = "hello"; // s 是有效的
    // 使用 s
} // 作用域结束,s 被丢弃

fn a() {
   let s = "hello"; // s 是有效的 
}
// s 

3. 所有权的转移:移动(Move)

当变量赋值给另一个变量时,所有权会发生转移:

let a = 1;
let b =a;
let s1 = String::from("hello");
let s2 = s1;
// 现在 s1 无效,所有权转移到了 s2

在这段代码中,s1 将其所有权转移给了 s2,因此在 s1 被使用之后会导致编译错误。


课后习题

用2种方式实现,确保 s1 s2 都能正常打印出来

    // TODO:
    fn take_ownership(s: String) -> String {
        s
    }
    let s1 = String::from("Hello");
    let s2 = take_ownership(s1);


    // 如下代码不能修改 
    println!("{}", s1); 
    println!("{}", s2);


引用 References

  • 不可变引用(Immutable Reference):通过不可变引用,可以读取数据,但不能修改数据。一个变量可以有多个不可变引用,但不能与可变引用共存。
  • 可变引用(Mutable Reference):通过可变引用,可以读取和修改数据。一个变量在某一时刻只能有一个可变引用,且不能与不可变引用共存。

引用的规则

  • 同一时间内,一个变量只能有一个可变引用或多个不可变引用。
  • 引用必须总是有效。

引用规则 vs 所有权规则

    let mut s = String::from("hello");

    let r1 = &s; 
    let r2 = &s; 
    println!("{r1} and {r2}");
    let r3 = &mut s; 
    println!("{r3}");

move & borrowing & referencing

  • move 堆数据所有权
  • borrowing 函数
  • referencing 变量

切片(Slices)

  • 字符串切片
  • 数组切片

字符串切片是对字符串部分内容的引用:

let s = String::from("hello world");
let hello = &s[0..5]; // 引用 "hello"
let world = &s[6..11]; // 引用 "world"
数组切片

数组切片与字符串切片类似:

let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // 引用 [2, 3]

生命周期(Lifetimes)

dangling referencing

生命周期是Rust用来保证引用有效性的机制。生命周期注解允许编译器推断引用的有效范围,确保在引用仍然有效时使用它们。

#[test]
fn test_dangling_pointer() {
    let a = get_a();

    fn get_a() -> &String {
        &"a".to_string()
    }
}

课后习题

通过编译器错误提示,修复并运行代码

#[test]
fn test_lifetime() {
    let large = longest("a", "ab");
    println!("large one is {large}");

    fn longest(x: &str, y: &str) -> &str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
}
Last Updated:
Contributors: rosendo
Prev
niche optimization
Next
模式匹配(pattern matching)