You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
6.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# &'static 和 T: 'static
Rust 的难点之一就在于它有不少容易混淆的概念,例如 `&str` 、`str` 与 `String` 再比如本文标题那两位。不过与字符串也有不同,这两位对于普通用户来说往往是无需进行区分的,但是当大家想要深入学习或使用 Rust 时,它们就会成为成功路上的拦路虎了。
与生命周期的其它章节不同本文短小精悍阅读过程可谓相当轻松愉快话不多说let's go。
`'static` 在 Rust 中是相当常见的,例如字符串字面值就具有 `'static` 生命周期:
```rust
fn main() {
let mark_twain: &str = "Samuel Clemens";
print_author(mark_twain);
}
fn print_author(author: &'static str) {
println!("{}", author);
}
```
除此之外,特征对象的生命周期也是 `'static`,例如[这里](https://course.rs/compiler/fight-with-compiler/lifetime/closure-with-static.html#特征对象的生命周期)所提到的。
除了 `&'static` 的用法外,我们在另外一种场景中也可以见到 `'static` 的使用:
```rust
use std::fmt::Display;
fn main() {
let mark_twain = "Samuel Clemens";
print(&mark_twain);
}
fn print<T: Display + 'static>(message: &T) {
println!("{}", message);
}
```
在这里,很明显 `'static` 是作为生命周期约束来使用了。 **那么问题来了, `&'static``T: 'static` 的用法到底有何区别?**
## `&'static`
`&'static` 对于生命周期有着非常强的要求:一个引用必须要活得跟剩下的程序一样久,才能被标注为 `&'static`
对于字符串字面量来说,它直接被打包到二进制文件中,永远不会被 `drop`,因此它能跟程序活得一样久,自然它的生命周期是 `'static`
但是,**`&'static` 生命周期针对的仅仅是引用,而不是持有该引用的变量,对于变量来说,还是要遵循相应的作用域规则** :
```rust
use std::{slice::from_raw_parts, str::from_utf8_unchecked};
fn get_memory_location() -> (usize, usize) {
// “Hello World” 是字符串字面量,因此它的生命周期是 `'static`.
// 但持有它的变量 `string` 的生命周期就不一样了,它完全取决于变量作用域,对于该例子来说,也就是当前的函数范围
let string = "Hello World!";
let pointer = string.as_ptr() as usize;
let length = string.len();
(pointer, length)
// `string` 在这里被 drop 释放
// 虽然变量被释放,无法再被访问,但是数据依然还会继续存活
}
fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
// 使用裸指针需要 `unsafe{}` 语句块
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
);
// 如果大家想知道为何处理裸指针需要 `unsafe`,可以试着反注释以下代码
// let message = get_str_at_location(1000, 10);
}
```
上面代码有两点值得注意:
- `&'static` 的引用确实可以和程序活得一样久,因为我们通过 `get_str_at_location` 函数直接取到了对应的字符串
- 持有 `&'static` 引用的变量,它的生命周期受到作用域的限制,大家务必不要搞混了
## `T: 'static`
相比起来,这种形式的约束就有些复杂了。
首先,在以下两种情况下,`T: 'static` 与 `&'static` 有相同的约束:`T` 必须活得和程序一样久。
```rust
use std::fmt::Debug;
fn print_it<T: Debug + 'static>( input: T) {
println!( "'static value passed in is: {:?}", input );
}
fn print_it1( input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}
fn main() {
let i = 5;
print_it(&i);
print_it1(&i);
}
```
以上代码会报错,原因很简单: `&i` 的生命周期无法满足 `'static` 的约束,如果大家将 `i` 修改为常量,那自然一切 OK。
见证奇迹的时候,请不要眨眼,现在我们来稍微修改下 `print_it` 函数:
```rust
use std::fmt::Debug;
fn print_it<T: Debug + 'static>( input: &T) {
println!( "'static value passed in is: {:?}", input );
}
fn main() {
let i = 5;
print_it(&i);
}
```
这段代码竟然不报错了!原因在于我们约束的是 `T`,但是使用的却是它的引用 `&T`,换而言之,我们根本没有直接使用 `T`,因此编译器就没有去检查 `T` 的生命周期约束!它只要确保 `&T` 的生命周期符合规则即可,在上面代码中,它自然是符合的。
再来看一个例子:
```rust
use std::fmt::Display;
fn main() {
let r1;
let r2;
{
static STATIC_EXAMPLE: i32 = 42;
r1 = &STATIC_EXAMPLE;
let x = "&'static str";
r2 = x;
// r1 和 r2 持有的数据都是 'static 的,因此在花括号结束后,并不会被释放
}
println!("&'static i32: {}", r1); // -> 42
println!("&'static str: {}", r2); // -> &'static str
let r3: &str;
{
let s1 = "String".to_string();
// s1 虽然没有 'static 生命周期,但是它依然可以满足 T: 'static 的约束
// 充分说明这个约束是多么的弱。。
static_bound(&s1);
// s1 是 String 类型,没有 'static 的生命周期,因此下面代码会报错
r3 = &s1;
// s1 在这里被 drop
}
println!("{}", r3);
}
fn static_bound<T: Display + 'static>(t: &T) {
println!("{}", t);
}
```
## static 到底针对谁?
大家有没有想过,到底是 `&'static` 这个引用还是该引用指向的数据活得跟程序一样久呢?
**答案是引用指向的数据**,而引用本身是要遵循其作用域范围的,我们来简单验证下:
```rust
fn main() {
{
let static_string = "I'm in read-only memory";
println!("static_string: {}", static_string);
// 当 `static_string` 超出作用域时,该引用不能再被使用,但是数据依然会存在于 binary 所占用的内存中
}
println!("static_string reference remains alive: {}", static_string);
}
```
以上代码不出所料会报错,原因在于虽然字符串字面量 "I'm in read-only memory" 的生命周期是 `'static`,但是持有它的引用并不是,它的作用域在内部花括号 `}` 处就结束了。
## 总结
总之, `&'static``T: 'static` 大体上相似,相比起来,后者的使用形式会更加复杂一些。
至此,相信大家对于 `'static``T: 'static` 也有了清晰的理解,那么我们应该如何使用它们呢?
作为经验之谈,可以这么来:
- 如果你需要添加 `&'static` 来让代码工作,那很可能是设计上出问题了
- 如果你希望满足和取悦编译器,那就使用 `T: 'static`,很多时候它都能解决问题
> 一个小知识,在 Rust 标准库中,有 48 处用到了 &'static 112 处用到了 `T: 'static` ,看来取悦编译器不仅仅是菜鸟需要的,高手也经常用到 :)