Rust 所有权

6 阅读3分钟

Rust 的所有权(Ownership)是该语言的核心特性之一,是 Rust 无需垃圾回收器就能保证内存安全的关键机制。

所有权规则

Rust 所有权系统基于以下三条核心规则:

  1. 每个值都有一个变量作为其所有者:在 Rust 中,当你创建一个值(如整数、字符串等)时,必须将其绑定到一个变量上,这个变量就是该值的所有者。例如:
let s = String::from("hello"); 

这里变量 s 就是字符串 "hello" 的所有者。 2. 同一时间,一个值只能有一个所有者:这意味着一个值不能同时被多个变量直接拥有。例如,不能有两个变量同时完全拥有同一个 String 类型的值。 3. 3. 当所有者离开作用域时,值将被丢弃:作用域是程序中变量有效的范围。当变量离开其作用域时,Rust 会自动调用 drop 函数释放该值所占用的内存。例如:

{ 
    let s = String::from("hello"); // s 在此作用域内有效 
} // s 离开作用域,其占用的内存被释放 

所有权转移

当把一个值赋值给另一个变量,或者将值作为参数传递给函数时,所有权会发生转移。这种机制避免了内存的双重释放问题。

赋值时所有权转移

let s1 = String::from("hello"); 
let s2 = s1; // 此时 s1 不再有效,所有权已转移到 s2 
// println!("{}", s1); // 这行代码会报错 
println!("{}", s2); 

在这个例子中,当执行 let s2 = s1; 时,s1 对字符串的所有权转移到了 s2s1 不再拥有该字符串,后续使用 s1 会导致编译错误。

函数调用时所有权转移

fn take_ownership(s: String) { 
    println!("{}", s); 
} // s 离开作用域,其占用的内存被释放 
let s = String::from("hello"); 
take_ownership(s); // 此时 s 不再有效,所有权已转移到函数内部 
// println!("{}", s); // 这行代码会报错 

当调用 take_ownership(s) 时,s 的所有权转移到了函数的参数 s 上,函数执行完毕后,参数 s 离开作用域,其占用的内存被释放。 ### 引用与借用 为了在不转移所有权的情况下使用值,Rust 引入了引用(Reference)和借用(Borrowing)的概念。引用允许你使用值,但不拥有它。

不可变引用

fn calculate_length(s: &String) -> usize { 
    s.len() 
} // 这里 s 是引用,不会释放其指向的值的内存 
let s = String::from("hello"); 
let len = calculate_length(&s); 
println!("The length of '{}' is {}.", s, len); 

在这个例子中,&s 创建了 s 的一个不可变引用,传递给 calculate_length 函数。函数使用这个引用计算字符串的长度,而不获取其所有权。函数执行完毕后,s 仍然有效。

可变引用

可变引用允许你修改所引用的值,但有一些限制:同一时间,对于一个特定的数据,只能有一个可变引用,或者有任意数量的不可变引用,但不能同时拥有可变引用和不可变引用。

fn change(some_string: &mut String) { 
    some_string.push_str(", world"); 
} 
let mut s = String::from("hello"); 
change(&mut s); 
println!("{}", s); 

在这个例子中,&mut s 创建了 s 的一个可变引用,传递给 change 函数,函数可以修改 s 的值。 ### 切片类型 切片(Slice)是一种没有所有权的数据类型,它引用集合中的一部分元素。例如,字符串切片是 &str 类型:

let s = String::from("hello world"); 
let hello = &s[0..5]; 
let world = &s[6..11]; 
println!("{} {}", hello, world); 

在这个例子中,&s[0..5]&s[6..11] 分别创建了字符串 s 的两个切片,它们引用了 s 中的部分字符,而不拥有这些字符。 通过所有权、引用和切片等机制,Rust 在编译时就能确保内存安全,避免了许多常见的内存错误,如空指针引用、悬垂指针和内存泄漏等。