作用域
作用域在所有权、借用和生命周期中扮演着重要的角色。也就是说,它指示编译器何时允许借用、何时可以释放资源以及何时变量被创建或销毁。
RAII
在 Rust 中,变量不仅仅在栈中保存数据:它们还拥有资源,例如 Box<T>
拥有堆中的内存。Rust 强制执行 RAII(资源获取即初始化),因此每当对象超出范围时,都会调用其析构函数并释放其资源。
这种行为可防止资源泄漏,用户自也不必手动释放内存或担心内存泄漏问题。
fn create_box() {
// 在堆上分配一个整数
let _1 = Box::new(58i32);
// `_1` 在这里被销毁,内存被释放
}
fn main() {
// 在堆上分配一个整数
let _2 = Box::new(5i32);
// 创建大量的 Box
// 无需手动释放内存!
for _ in 0..1_000 {
create_box();
}
// `_2` 在这里被销毁,内存被释放
}
析构函数
在 Rust 中,析构函数的概念是通过 Drop
特征提供的。当资源超出范围时,将调用析构函数。
并不是每种类型都需要实现此特征,只有在需要其特有的析构逻辑时才需要实现它。
struct HasDrop;
impl Drop for HasDrop {
fn drop(&mut self) {
println!("Dropping HasDrop!");
}
}
struct HasTwoDrops {
one: HasDrop,
two: HasDrop,
}
impl Drop for HasTwoDrops {
fn drop(&mut self) {
println!("Dropping HasTwoDrops!");
}
}
fn main() {
let _x = HasTwoDrops { one: HasDrop, two: HasDrop };
println!("Running!");
}
所有权
因为变量负责释放自己的资源,资源只能有一个所有者。这可防止资源被多次释放。
在进行赋值(let a = b
)或按值传递函数参数(func(v)
)时,资源的所有权被转移。在 Rust 中,这被称为 move。
fn use_box(c: Box<i32>) {
println!("Destroying a box that contains {}", c);
}
fn main() {
// `a` 是指向堆中分配的整数的指针
let a = Box::new(58i32);
// 将 `a` 转移到 `b`
let b = a;
// error[E0382]: borrow of moved value: `a`
println!("a -> {}", a);
use_box(b); // Destroying a box that contains 58
// error[E0382]: borrow of moved value: `b`
println!("b -> {}", b);
}
借用
大多数情况下,用户希望在不获取所有权的情况下访问数据。为了实现这一点,Rust 使用了借用机制。与通过值传递对象不同(T
),对象可以通过引用传递(&T
)。
// 此函数获取一个 box 的所有权并销毁它
fn useup_box_i32(boxed_i32: Box<i32>) {
println!("Destroying box that contains {}", boxed_i32);
}
// 此函数借用一个 i32
fn borrow_i32(borrowed_i32: &i32) {
println!("This int is: {}", borrowed_i32);
}
fn main() {
// 在堆中创建一个 boxed i32,并在堆栈中创建一个 i32
let boxed_i32 = Box::new(5_i32);
let stacked_i32 = 8_i32;
// 借用 box 的内容,所有权没有转移。
// 因此其内容可以再次被借用。
borrow_i32(&boxed_i32); // This int is: 5
borrow_i32(&stacked_i32); // This int is: 8
// `boxed_i32` 现在可以转移所有权给 `useup_box_i32` 并被其销毁
useup_box_i32(boxed_i32); // Destroying box that contains 5
}
可变引用
可变数据可以通过 &mut T
进行可变借用。这称为可变引用,它为借用者提供了读/写权限。
#[derive(Clone, Copy)]
struct Album {
// `&'static str` 是对在只读内存中分配的字符串的引用
singer: &'static str,
title: &'static str,
year: u32,
}
// 此函数接受对 Album 的引用
fn borrow_album(album: &Album) {
println!("I immutably borrowed {} - {} edition", album.title, album.year);
}
// 此函数接受对可变 Album 的引用,并将 `year` 更改为 2014
fn new_edition(album: &mut Album) {
album.year = 2014;
println!("I mutably borrowed {} - {} edition", album.title, album.year);
}
fn main() {
let immut_album = Album {
// 字符串字面量的类型是 `&'static str`
singer: "Taylor Swift",
title: "reputation",
year: 2011,
};
// 创建一个可变副本
let mut mut_album = immut_album;
// 不可变借用不可变对象
borrow_album(&immut_album);
// I immutably borrowed reputation - 2011 edition
// 不可变借用可变对象
borrow_album(&mut_album);
// I immutably borrowed reputation - 2011 edition
// 可变借用一个可变对象
new_edition(&mut mut_album);
// I mutably borrowed reputation - 2014 edition
// error[E0596]: cannot borrow `immut_album` as mutable
new_edition(&mut immut_album);
}
生命周期
生命周期是一种编译器用来确保所有借用有效的机制构造。具体来说,变量的生命周期始于其创建时,结束于其销毁时。
显式注解
// `succeeded_borrow` 接受两个具有不同生命周期 `'a` 和 `'b` 的 `i32` 引用。
// 这两个生命周期必须都至少与函数 `succeeded_borrow` 的生命周期一样长。
fn succeeded_borrow<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
// 一个没有参数但带有生命周期参数 `'a` 的函数。
fn failed_borrow<'a>() {
let _x = 12;
// error[E0597]: `_x` does not live long enough
let y: &'a i32 = &_x;
// 在函数内部使用生命周期 `'a` 作为显式类型注解将失败,因为 `&_x` 的生命周期比 `y` 的生命周期短。
// 无法将短生命周期强制转换为长生命周期。
}
fn main() {
let (five, eight) = (5, 8);
// 将这两个变量的借用 (`&`) 传递到函数中。
succeeded_borrow(&five, &eight);
// 任何被借用的输入都必须比借用者的生命周期更长。
// 换句话说,`five` 和 `eight` 的生命周期必须比 `succeeded_borrow` 更长。
failed_borrow();
// 此处 `failed_borrow` 中没有强制 `'a` 比函数的生命周期更长,
// 但 `'a` 的生命周期确实更长。因为没约束的生命周期默认为 `'static`。
}
结构体生命周期
结构体中的生命周期注解与函数类似。
// 此结构体的两个字段引用生命周期必须都超出此结构体的生命周期。
#[derive(Debug)]
struct NamedBorrowed<'a> {
x: &'a i32,
y: &'a i32,
}
#[derive(Debug)]
enum Either<'a> {
Num(i32),
Ref(&'a i32),
}
fn main() {
let (x, y) = (5, 8);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is not borrowed in {:?}", number);
}
静态生命周期
Rust 有一些专有的生命周期名称,其中之一就是 'static
。
// 有 'static 生命周期的引用
let s: &'static str = "hello world";
// 'static 作为 trait 约束的一部分
fn generic<T>(x: T) where T: 'static {}
引用生命周期
有两种常见的方式可以创建 'static
生命周期的变量,两者都存储在最终产出二进制文件的只读存储中(即静态数据区):
- 使用
static
声明创建一个常量。 - 创建一个
string
字面量,其类型为:&'static str
。
static NUM: i32 = 18;
let s = "I'm a string literal";
Trait 约束
static
用作 trait 约束时表示该类型不包含任何非静态引用。
use std::fmt::Debug;
fn print_it( input: impl Debug + 'static ) {
println!("'static value: {:?}", input);
}
fn main() {
// i 有所有权并且不包含任何引用,因此它是 'static 的
let i = 58;
print_it(i); // 'static value: 58
// &i 并非 'static
// error[E0597]: `i` does not live long enough
print_it(&i);
}
一些生命周期模式很常见,因此编译器中借用检查器会允许用户省略它们,以便节省输入并提高可读性。这被称为生命周期省略
。
代码挑战
尝试修改编辑器中提供的代码以添加合适的生命周期。