前言
Rust
的生命周期概念主要是避免空引用,对用户来说空引用会导致程序崩溃,对于恶意攻击者来说这是个漏洞。
注意:空引用 是大多数软件错误的根源。据
Chromium
团队的调查,70%的严重事故都是内存安全类的(详见),如果你对此感兴趣,可以读一下 这篇文章 ,该文章介绍了浏览器内存安全问题
内存安全问题对于没有使用过类似C
语言进行内存管理的开发人员来说有点陌生,JavaScript
和Java
会利用垃圾回收机制帮你管理内存,避免空引用问题发生,垃圾回收器会追踪你对数据的引用,直到数据的引用数量是0
在63年前,垃圾回收机制还非常具有非常革命性,但它是有代价的,在极端情况下,垃圾回收会使程序失去几秒钟响应
Rust
不采用垃圾回收机制来避免空引用,你可以获得类似C
语言一般的高效同时具有JavaScript
语言般的安全性
注意:网上有许多介绍
Rust
生命周期的文章,本教程不会面面俱到,只会告诉你一些容易引起困惑的问题,教程后面的相关阅读是阅读本文的先决条件,请仔细阅读
正文
生命周期 & 生命周期注解
你经常会在一些文章中遇到将“生命周期注解”简写为“生命周期”,但这只是会让人感到困惑
- 生命周期:
Rust
借用检查器(borrow checker)中涉及到的一个概念,每个变量都有一个创建和销毁的时间点,这段时间就是它的生命周期 - 生命周期注解:一种
Rust
的语法,开发人员可以向引用添加一个命名标记以赋予其生命周期,使用场景是多个引用的时候才有意义
当你读到“你必须指定生命周期”或“给引用一个生命周期”的时候,这里的“生命周期”实际上就是“生命周期注解”
生命周期注解省略
每个引用都有一个生命周期,这和有没有注解无关,有时候不写注解并不意味着在逃避生命周期这回事
例如这个函数:
fn omits_annotations(list: &[String]) -> Option<&String> {
list.get(0)
}
与下面这个行数是等效的:
fn has_annotations<'a>(list: &'a [String]) -> Option<&'a String> {
list.get(1)
}
使用起来也是一致的:
fn main() {
let authors = vec!["Samuel Clemens".to_owned(), "Jane Austen".to_owned()];
let value = omits_annotations(&authors).unwrap();
println!("The first author is '{}'", value);
let value = has_annotations(&authors).unwrap();
println!("The second author is '{}'", value);
}
输出结果:
The first author is 'Samuel Clemens'
The second author is 'Jane Austen'
之所至可以省略注解是因为这里的入参和返回值都只有一个,此时Rust
会自动推断他们的生命周期是一致的
静态声明周期('static)
Rust
社区里经常会涉及到'static
,有很多解释'static
的内容,不过这会让人觉的更复杂了,下面我们尽可能简洁的介绍
有两种'static
的经典使用场景:
(一) 作为引用的显式生命期注释
fn main() {
let mark_twain = "Samuel Clemens";
print_author(mark_twain);
}
fn print_author(author: &'static str) {
println!("{}", author);
}
(二)作为泛型参数的生命周期限制
fn print<T: Display + 'static>(message: &T) {
println!("{}", message);
}
&'static
的出现意味着这个引用在程序的剩余部分也是有效的,其所指向的数据不能move
也不能被改变。字符串字面量都是&'static
的,
&'static
针对的不是保存引用的变量,我们以一个例子来做说明。get_memory_location()
函数返回一个字符串字面量的指针和长度,get_str_at_location()
函数以指针和长度作为参数,并读取内存中指定位置的内容
use std::{slice::from_raw_parts, str::from_utf8_unchecked};
fn get_memory_location() -> (usize, usize) {
let string = "Hello World!";
let pointer = string.as_ptr() as usize;
let length = string.len();
(pointer, length)
// `string` is dropped here.
// It's no longer accessible, but the data lives on.
}
fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
// Notice the `unsafe {}` block. We can't do things like this without
// acknowledging to Rust that we know this is dangerous.
unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) }
}
fn main() {
let (pointer, length) = get_memory_location();
let message = get_str_at_location(pointer, length);
println!(
"The {} bytes at 0x{:X} stored: {}",
length, pointer, message
);
// If you want to see why dealing with raw pointers is dangerous,
// uncomment this line.
// let message = get_str_at_location(1000, 10);
}
输出结果是:
The 12 bytes at 0x562037200057 stored: Hello World!
另一方面,添加'static
作为限制也就是在告诉Rust
你希望这个类型可以一直存在,而不是数据一直存在
你需要明白&'static
不等于 T: 'static
下面的代码证实了第二块中的String
是如何满足static_bound()
函数的'static
约束,但不保留第一个块中&'static
的相同性质
use std::fmt::Display;
fn main() {
let r1;
let r2;
{
static STATIC_EXAMPLE: i32 = 42;
r1 = &STATIC_EXAMPLE;
let x = "&'static str";
r2 = x;
}
println!("&'static i32: {}", r1);
println!("&'static str: {}", r2);
let r3;
{
let string = "String".to_owned();
static_bound(&string); // This is *not* an error
r3 = &string; // *This* is
}
println!("{}", r3);
}
fn static_bound<T: Display + 'static>(t: &T) {
println!("{}", t);
}
结果:
error[E0597]: `string` does not live long enough
--> crates/day-16/static/src/main.rs:21:10
|
21 | r3 = &string;
| ^^^^^^^ borrowed value does not live long enough
22 | }
| - `string` dropped here while still borrowed
23 | println!("{}", r3);
| -- borrow later used here
For more information about this error, try `rustc --explain E0597`.
虽然两种用法是有关联,但背后的精神却不一样
如果你想通过添加&'static
来解决问题,那么你需要重新思考看看这么做对不对,如果你需要添加'static
解决问题,那通常是没问题的
相关阅读
- The Rust Book: ch 10.03 - Validating References with Lifetimes
- Rust by Example: Lifetimes
- Rust Reference: Trait and lifetime bounds
- Rust Reference: Lifetime elision
- Rustonomicon: Lifetimes
- Common Rust Lifetime Misconceptions
- rustviz: Rust lifetime visualizer
- Understanding lifetimes in Rust
总结
本文历经五次迭代,让人最头痛的地方在于泛型、生命周期、引用在第三方库及异步代码中最佳实践,这些我们还未涉及。
后面我们会介绍到如何用Rust
提升JavaScript
程序的性能,如何建立一个web
服务器,如何实现文件读写操作以及序列化反序列化JSON