&'static 和 T: 'static
Rust 的难点之一就在于它有不少容易混淆的概念,例如
&str 、str 与 String,
再比如本文标题那两位。不过与字符串也有不同,这两位对于普通用户来说往往是无需进行区分的,但是当大家想要深入学习或使用
Rust 时,它们就会成为成功路上的拦路虎了。
'static 在 Rust 中是相当常见的,例如字符串字面值就具有
'static 生命周期:
| 12
 3
 4
 5
 6
 7
 
 | fn main() {let mark_twain: &str = "Samuel Clemens";
 print_author(mark_twain);
 }
 fn print_author(author: &'static str) {
 println!("{}", author);
 }
 
 | 
除此之外,特征对象的生命周期也是 'static,例如这里所提到的。
除了 &'static
的用法外,我们在另外一种场景中也可以见到 'static
的使用:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 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 的用法到底有何区别?
1. &'static
&'static
对于生命周期有着非常强的要求:一个引用必须要活得跟剩下的程序一样久,才能被标注为
&'static。
对于字符串字面量来说,它直接被打包到二进制文件中,永远不会被
drop,因此它能跟程序活得一样久,自然它的生命周期是
'static。
但是,&'static
生命周期针对的仅仅是引用,而不是持有该引用的变量,对于变量来说,还是要遵循相应的作用域规则
:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | 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)
 
 
 }
 
 fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
 
 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
 );
 
 
 }
 
 | 
上面代码有两点值得注意:
- &'static的引用确实可以和程序活得一样久,因为我们通过- get_str_at_location函数直接取到了对应的字符串
- 持有 &'static引用的变量,它的生命周期受到作用域的限制,大家务必不要搞混了
2. T: 'static
相比起来,这种形式的约束就有些复杂了。
首先,在以下两种情况下,T: 'static 与
&'static 有相同的约束:T
必须活得和程序一样久。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 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 函数:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 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
的生命周期符合规则即可,在上面代码中,它自然是符合的。
再来看一个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | 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: &str;
 
 {
 let s1 = "String".to_string();
 
 
 
 static_bound(&s1);
 
 
 r3 = &s1;
 
 
 }
 println!("{}", r3);
 }
 
 fn static_bound<T: Display + 'static>(t: &T) {
 println!("{}", t);
 }
 
 | 
3. static 到底针对谁?
大家有没有想过,到底是 &'static
这个引用还是该引用指向的数据活得跟程序一样久呢?
答案是引用指向的数据,而引用本身是要遵循其作用域范围的,我们来简单验证下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | fn main() {{
 let static_string = "I'm in read-only memory";
 println!("static_string: {}", static_string);
 
 
 }
 
 println!("static_string reference remains alive: {}", static_string);
 }
 
 | 
以上代码不出所料会报错,原因在于虽然字符串字面量 "I'm in read-only
memory" 的生命周期是
'static,但是持有它的引用并不是,它的作用域在内部花括号
} 处就结束了。
4. 总结
总之, &'static 和 T: 'static
大体上相似,相比起来,后者的使用形式会更加复杂一些。
至此,相信大家对于 'static 和 T: 'static
也有了清晰的理解,那么我们应该如何使用它们呢?
作为经验之谈,可以这么来:
- 如果你需要添加 &'static来让代码工作,那很可能是设计上出问题了
- 如果你希望满足和取悦编译器,那就使用
T: 'static,很多时候它都能解决问题
一个小知识,在 Rust 标准库中,有 48 处用到了 &'static ,112
处用到了 T: 'static
,看来取悦编译器不仅仅是菜鸟需要的,高手也经常用到 :)