【Rust学习】内存安全探秘:变量的所有权、引用与借用
字数 1047 2025-08-11 17:40:19
Rust内存安全机制:所有权、引用与借用详解
一、Rust语言概述
Rust是一种高效、可靠的通用高级语言,由Mozilla开发,最早发布于2014年9月。其核心特性包括:
- 高性能:Rust速度惊人且内存利用率极高,没有运行时和垃圾回收
- 可靠性:丰富的类型系统和所有权模型保证内存安全和线程安全
- 生产力:出色的文档、友好的编译器、清晰的错误提示和一流的工具链
Rust在Linux内核开发中获得了原生支持(Linux 6.1起),这标志着它已成为系统编程的重要选择。Google Android团队也广泛使用Rust,实践证明它能显著减少内存安全漏洞。
二、所有权机制
2.1 所有权基本规则
Rust通过所有权系统管理内存,编译时检查以下规则:
- Rust中的每一个值都有一个所有者(owner)
- 值在任一时刻有且只有一个所有者
- 当所有者(变量)离开作用域,这个值将被丢弃
2.2 栈(Stack)与堆(Heap)
- 栈:后进先出结构,存储已知固定大小的数据,操作快速
- 堆:缺乏组织,存储编译时大小未知或可能变化的数据,通过指针访问
2.3 String类型示例
字符串字面值(硬编码)与String类型(堆分配)的区别:
let s = "hello"; // 字符串字面值,不可变
let mut s = String::from("hello"); // String类型,可变
s.push_str(", world!");
2.4 变量与数据交互
移动(Move)
let s1 = String::from("hello");
let s2 = s1; // s1被移动到s2,s1不再有效
移动操作只复制指针、长度和容量(栈数据),不复制堆数据,避免浅拷贝问题。
克隆(Clone)
let s1 = String::from("hello");
let s2 = s1.clone(); // 深度复制堆数据
拷贝(Copy)
简单标量类型(整数、布尔、浮点、字符等)实现Copy trait,赋值后原变量仍有效:
let x = 5;
let y = x; // x仍然有效
2.5 所有权与函数
函数传参会转移所有权,返回值也可转移所有权:
fn main() {
let s = String::from("hello");
takes_ownership(s); // s的值移动进函数
let x = 5;
makes_copy(x); // x是Copy的,之后仍可用
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string离开作用域,drop被调用
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer离开作用域,无特殊操作
三、引用与借用
3.1 基本引用
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 创建引用
println!("Length: {}", len);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s离开作用域,但因为它不拥有所有权,不丢弃指向的数据
3.2 可变引用
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
3.3 引用规则
- 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
- 引用必须总是有效的
3.4 悬垂引用
Rust编译器防止创建悬垂引用:
fn dangle() -> &String { // 错误!
let s = String::from("hello");
&s
} // s离开作用域被丢弃,返回的引用无效
正确做法是直接返回String:
fn no_dangle() -> String {
let s = String::from("hello");
s
}
四、Slice类型
Slice允许引用集合中一段连续的元素序列,而不引用整个集合。
4.1 字符串Slice
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
简化语法:
let slice = &s[..2]; // 从开始到索引2
let slice = &s[3..]; // 从索引3到结束
let slice = &s[..]; // 整个字符串
4.2 改进的first_word函数
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[..]
}
4.3 其他类型Slice
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 类型为&[i32]
assert_eq!(slice, &[2, 3]);
五、总结
Rust的所有权、借用和slice机制提供了以下优势:
- 编译时确保内存安全,无需垃圾回收
- 避免数据竞争和悬垂指针等常见问题
- 通过编译器检查而非运行时开销保证安全性
- 提供灵活的数据访问方式(如slice)而不牺牲安全性
这些特性使Rust成为系统编程的理想选择,特别是在需要高性能和高可靠性的场景中。