渗入生命的铁锈味——rust学习笔记

Wang1r Lv3

铁锈味是血液的味道,虽然我不知道rust的命名是否与此相关,但我觉得这样想似乎也无伤大雅。毕竟学起来真的有点想吐血()

初学rust,它带给我最大的感受是编译器近乎苛刻的严格。诚然,这样的严格能从一开始就避免一些问题,但未免有些束缚住程序员的思维。不过就我而言,在对内存管理完全没有信心的情况下,选择rust的确是个正确的选择。

就像现实中的很多情况一样,rust的很多要求既是禁锢也是保护。这让我想起中学时的命题作文,就像当时对其的比喻一样,这也是“带着镣铐跳舞”。

保证内存安全

默认不可变性

为了保证内存安全,rust中变量默认是不可变的,这意味着一旦变量被赋值,它的值就不能被修改。如果需要修改变量的值,必须显式地使用 mut 关键字声明其为可变的。

1
2
3
let x = 5; // x 是不可变的
let mut y = 10; // y 是可变的
y = 15; // 可以修改 y 的值

这样可以很明确地标注出哪些变量是将要被改变的,对编程过程也有一定帮助(虽然好像和其他语言用const关键字标记不变起到的作用差不多)

所有权系统

单所有者原则

每个资源在任何时候都有一个且只有一个所有者。当所有者超出作用域时,资源会被自动释放。这个原则通过变量的绑定和解绑定来实现。

1
2
3
4
5
{
let s = String::from("hello"); // s 是字符串 "hello" 的所有者
// 在这个作用域内可以使用 s
}
// s 的作用域结束,字符串 "hello" 被自动释放

移动语义

当一个资源的所有权从一个变量移交给另一个变量时,原来的变量将不再有效,以防止资源的双重释放(double free)问题。这称为移动语义。(突然想起写C++时总是二次析构的痛苦回忆)

1
2
3
4
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被移动到 s2,s1 不再有效
// 下面这行代码会导致编译错误:use of moved value
// println!("{}", s1);

拷贝语义

对于简单的标量类型(如整数、浮点数、布尔值和字符)以及实现了 Copy 特征的类型,Rust 会使用拷贝语义,而不是移动语义。这意味着这些类型的值在赋值时会被拷贝,而不是移动。

1
2
let x = 5;
let y = x; // y 拷贝 x 的值,x 仍然有效

借用和生命周期

借用(Borrowing)

你可以在不转移所有权的情况下临时借用资源。借用通过引用(&)实现,分为不可变引用(&T)和可变引用(&mut T)。

1
2
3
let s = String::from("hello");
let s_ref = &s; // s_ref 是 s 的不可变引用
let s_mut_ref = &mut s; // s_mut_ref 是 s 的可变引用

引用规则

Rust 的引用规则确保了引用的安全性:

  • 在同一作用域内,对于同一个资源,可以有多个不可变引用,但不能有可变引用。
  • 在同一作用域内,对于同一个资源,可以有一个可变引用,但不能有其他任何引用。
1
2
3
4
5
6
7
8
9
10
11
12
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
// let r3 = &mut s; // 错误,不能在同一作用域内同时存在不可变引用和可变引用

println!("{}, {}", r1, r2);

let r3 = &mut s; // 没问题,因为 r1 和 r2 的作用域已经结束
*r3 = String::from("world");

println!("{}", r3);

生命周期注解

生命周期注解用于显式地指明引用的生存期,确保引用在其所有者生存期内有效。编译器会自动推断大多数生命周期,但在某些复杂情况下需要手动注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

let s = String::from("hello world");
let word = first_word(&s); // word 是 s 的一个引用

在上面的例子中,first_word 函数接收一个字符串切片的引用,并返回一个字符串切片的引用。编译器会确保返回的引用不会超出 s 的生存期。

生命周期检查

Rust 的借用检查器在编译时会检查引用的生命周期,确保它们在其所有者生存期内有效。这避免了悬挂引用(dangling references)问题。

1
2
3
4
5
6
7
8
9
10
11
12
let s: String;

{
let r: &String;
s = String::from("hello");
r = &s; // r 借用 s 的值
// r 在这里仍然有效
}

// s 的作用域结束,s 被释放
// 下面这行代码会导致编译错误:use of dangling reference
// println!("{}", r);

智能指针和资源管理

Rust 使用智能指针(如 BoxRcArc)来管理资源,并通过它们的所有权和借用规则确保内存安全。

1
2
let x = Box::new(5); // x 是一个堆分配的整数
println!("{}", *x); // 解引用 x,访问其值

Rust与CTF

纵使rust有那么多保证内存安全的措施,但为了一些功能的实现,很多时候也很难保证不使用unsafe,这个时候就会出现可能的内存问题(所以unsafe是为了出问题可以追责?[思考])

  • 标题: 渗入生命的铁锈味——rust学习笔记
  • 作者: Wang1r
  • 创建于 : 2025-01-26 16:24:00
  • 更新于 : 2025-04-07 12:35:18
  • 链接: https://wang1rrr.github.io/2025/01/26/渗入生命的铁锈味——rust学习笔记/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。