案例一
画出生命周期
// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
fn main() {
let i = 3; // `i` 生命周期开始 ───────────────────────┐
{ // │
let borrow1 = &i; // `borrow1` 生命周期开始. ──┐ │
// │ │
println!("borrow1: {}", borrow1); // │ │
} // `borrow1` 生命周期结束. ──────────────────────┘ │
{ // │
let borrow2 = &i; // `borrow2` 生命周期开始. ──┐ │
// │ │
println!("borrow2: {}", borrow2); // │ │
} // `borrow2` 生命周期结束. ──────────────────────┘ │
} // `i` 生命周期结束 ────────────────────────────────────┘
// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
fn main() {
let i = 3; // `i` 生命周期开始 ───────────────────────┐
{ // │
let borrow1 = &i; // `borrow1` 生命周期开始. ──┐ │
// │ │
println!("borrow1: {}", borrow1); // │ │
} // `borrow1` 生命周期结束. ──────────────────────┘ │
{ // │
let borrow2 = &i; // `borrow2` 生命周期开始. ──┐ │
// │ │
println!("borrow2: {}", borrow2); // │ │
} // `borrow2` 生命周期结束. ──────────────────────┘ │
} // `i` 生命周期结束 ────────────────────────────────────┘
案例二
画出生命周期
fn main() {
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
} // 引用的生命周期 'a 比被引用对象的生命周期 'b 长,因此编译错误。
fn main() {
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
} // 引用的生命周期 'a 比被引用对象的生命周期 'b 长,因此编译错误。
案例三
标注生命周期使之正常运行
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里 longest
函数接受两个切片 x
和 y
的引用作为参数,然后根据长度返回其中较长的那个切片。
直接返回 x
或 y
是不正确的,因为 x
和 y
的生命周期是临时的,仅在函数作用域中有效。返回后就会失效造成悬空引用。
所以我们需要手动标注生命周期,以表示返回值生命周期可延续到函数作用域之外,这样编译就能通过。
返回值如果是引用,其生命周期必须在函数签名中声明
案例四
修复错误
fn invalid_output<'a>() -> &'a String {
&String::from("foo")
}
fn invalid_output<'a>() -> &'a String {
&String::from("foo")
}
这里 invalid_output
函数试图返回一个指向局部变量的静态生命周期引用。
由于 String::from("foo")
是在函数体内创建的,它的生命周期仅限于函数的作用域,当函数返回后,这个局部变量将被销毁,引用也将指向无效的内存区域,这将导致悬垂引用。
解决方式一:改变返回类型
直接返回 String 对象,而不是其引用
fn invalid_output() -> String {
String::from("foo")
}
fn invalid_output() -> String {
String::from("foo")
}
解决方式二:使用静态字符串
如果确实需要返回一个静态生命周期的字符串,可以使用字符串字面量。
fn invalid_output() -> &'static str {
"foo"
}
fn invalid_output() -> &'static str {
"foo"
}
案例五
// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
/* 让下面的代码工作 */
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` 活得不够久 borrowed value does not live long enough
let y: &'a i32 = &_x;
// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
// 你不能将一个小的生命周期强转成大的
}
fn main() {
let (four, nine) = (4, 9);
print_refs(&four, &nine);
// 这里,four 和 nice 的生命周期必须要比函数 print_refs 长
failed_borrow();
// `failed_borrow` 没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
}
// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
/* 让下面的代码工作 */
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` 活得不够久 borrowed value does not live long enough
let y: &'a i32 = &_x;
// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
// 你不能将一个小的生命周期强转成大的
}
fn main() {
let (four, nine) = (4, 9);
print_refs(&four, &nine);
// 这里,four 和 nice 的生命周期必须要比函数 print_refs 长
failed_borrow();
// `failed_borrow` 没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
}
这里 failed_borrow
函数中尝试创建一个指向局部变量 _x
的引用 y
,并将其标记为 'a
的生命周期。
然而局部变量 _x
的生命周期是函数 failed_borrow
的作用域,而 'a
生命周期没有受到任何限制,导致了生命周期不匹配的错误。
解决方式一:传递引用作为函数参数
将 _x
提升到函数之外,作为参数传递给函数 failed_borrow
,可以确保 y
生命周期不会超过 _x 的生命周期。
fn failed_borrow<'a>(x: &'a i32) {
// 使用 `x` 的引用,确保生命周期匹配
let y: &'a i32 = x;
}
fn main() {
let _x = 12;
failed_borrow(&_x);
}
fn failed_borrow<'a>(x: &'a i32) {
// 使用 `x` 的引用,确保生命周期匹配
let y: &'a i32 = x;
}
fn main() {
let _x = 12;
failed_borrow(&_x);
}
解决方式二:标记局部变量的生命周期为 'static
如果确定局部变量 _x
的生命周期是静态的,可以将其生命周期标记为 'static
,从而确保 y
生命周期不会超过 _x 的生命周期。
fn failed_borrow<'a>() {
// 将局部变量 `_x` 生命周期标记为 `'static`
let _x: &'static i32 = &12;
// 使用 `&_x` 的引用,它的生命周期是 `'static`
let y: &'a i32 = _x;
}
fn main() {
failed_borrow();
}
fn failed_borrow<'a>() {
// 将局部变量 `_x` 生命周期标记为 `'static`
let _x: &'static i32 = &12;
// 使用 `&_x` 的引用,它的生命周期是 `'static`
let y: &'a i32 = _x;
}
fn main() {
failed_borrow();
}
案例六
标注生命周期使之正常运行
// `i32` 的引用必须比 `Borrowed` 活得更久
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[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 = 18;
let y = 15;
let single = Borrowed(&x);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x is borrowed in {:?}", single);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is *not* borrowed in {:?}", number);
}
// `i32` 的引用必须比 `Borrowed` 活得更久
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[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 = 18;
let y = 15;
let single = Borrowed(&x);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x is borrowed in {:?}", single);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is *not* borrowed in {:?}", number);
}
struct 中需要注意:如果有字段是引用,则此字段需要比 struct 活的的更久。
案例七
修复错误
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType,
}
fn main() {
let var_a = 35;
let example: Example;
{
let var_b = NoCopyType {};
example = Example {
a: &var_a,
b: &var_b,
};
}
println!("(Success!) {:?}", example);
}
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType,
}
fn main() {
let var_a = 35;
let example: Example;
{
let var_b = NoCopyType {};
example = Example {
a: &var_a,
b: &var_b,
};
}
println!("(Success!) {:?}", example);
}
这段代码中,var_b
是个局部变量,其生命周期仅限于其所在的局部作用域。
Example
的实例 example
是在局部作用域之外声明并使用的,其生命周期超出了 var_b
。
因此如果将 &var_b
的引用传递给 Example
结构体,它在使用时将持有一个已经无效的引用,这将导致悬垂引用。
解决方式一:改变生命周期
fn main() {
let var_a = 35;
let example: Example;
// 将 var_b 提出局部作用域
let var_b = NoCopyType {};
{
example = Example {
a: &var_a,
b: &var_b,
};
}
println!("(Success!) {:?}", example);
}
fn main() {
let var_a = 35;
let example: Example;
// 将 var_b 提出局部作用域
let var_b = NoCopyType {};
{
example = Example {
a: &var_a,
b: &var_b,
};
}
println!("(Success!) {:?}", example);
}
解决方式二:转移所有权
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
struct Example<'a> {
a: &'a u32,
// 也可用 Box<NoCopyType> 类型,让 var_b 声明在堆上
b: NoCopyType,
}
fn main() {
let var_a = 35;
let example: Example;
{
let var_b = NoCopyType {};
example = Example {
a: &var_a,
// 不用引用,直接转移所有权
b: var_b,
};
}
println!("(Success!) {:?}", example);
}
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
struct Example<'a> {
a: &'a u32,
// 也可用 Box<NoCopyType> 类型,让 var_b 声明在堆上
b: NoCopyType,
}
fn main() {
let var_a = 35;
let example: Example;
{
let var_b = NoCopyType {};
example = Example {
a: &var_a,
// 不用引用,直接转移所有权
b: var_b,
};
}
println!("(Success!) {:?}", example);
}
案例八
修复错误
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
#[allow(dead_code)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType
}
/* 修复函数的签名 */
fn fix_me(foo: &Example) -> &NoCopyType {
foo.b
}
fn main() {
let no_copy = NoCopyType {};
let example = Example { a: &1, b: &no_copy };
fix_me(&example);
println!("Success!")
}
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
#[allow(dead_code)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType
}
/* 修复函数的签名 */
fn fix_me(foo: &Example) -> &NoCopyType {
foo.b
}
fn main() {
let no_copy = NoCopyType {};
let example = Example { a: &1, b: &no_copy };
fix_me(&example);
println!("Success!")
}
fix_me
函数需要显式标注生命周期,否则编译器无法正确推断引用的生命周期。
fn fix_me<'a, 'b>(foo: &'a Example<'a, 'b>) -> &'b NoCopyType {
foo.b
}
fn fix_me<'a, 'b>(foo: &'a Example<'a, 'b>) -> &'b NoCopyType {
foo.b
}
案例九
标注生命周期使之正常运行
struct ImportantExcerpt {
part: &str,
}
impl ImportantExcerpt {
fn level(&self) -> i32 {
3
}
}
fn main() {}
struct ImportantExcerpt {
part: &str,
}
impl ImportantExcerpt {
fn level(&self) -> i32 {
3
}
}
fn main() {}
结构体 ImportantExcerpt
中的字段 part
为切片引用,所以需要比 struct 活得更久,需要显式标注生命周期。
方法 level
的形参有使用 &self
引用,其需要和 part
字段有相同的生命周期,需要显式标注生命周期。
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
fn main() {}
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
fn main() {}
案例十
使用两种方法填空
fn main() {
__;
need_static(v);
println!("Success!")
}
fn need_static(r : &'static str) {
assert_eq!(r, "hello");
}
fn main() {
__;
need_static(v);
println!("Success!")
}
fn need_static(r : &'static str) {
assert_eq!(r, "hello");
}
解决方式一
字符串字面量是保存在二进制文件中,它的生命周期是 'static
fn main() {
let v = "hello";
need_static(v);
println!("Success!")
}
fn need_static(r : &'static str) {
assert_eq!(r, "hello");
}
fn main() {
let v = "hello";
need_static(v);
println!("Success!")
}
fn need_static(r : &'static str) {
assert_eq!(r, "hello");
}
解决方式二
使用 static
声明的字符串字面量生命周期是 'static
fn main() {
static v: &str = "hello";
need_static(v);
println!("Success!")
}
fn need_static(r: &'static str) {
assert_eq!(r, "hello");
}
fn main() {
static v: &str = "hello";
need_static(v);
println!("Success!")
}
fn need_static(r: &'static str) {
assert_eq!(r, "hello");
}
案例十一
让代码工作,但不要修改函数的签名
#[derive(Debug)]
struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
Some(&mut Config {
a: "A".to_string(),
b: "B".to_string(),
})
}
fn main() {
unsafe {
config = init();
println!("{:?}", config)
}
}
#[derive(Debug)]
struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
Some(&mut Config {
a: "A".to_string(),
b: "B".to_string(),
})
}
fn main() {
unsafe {
config = init();
println!("{:?}", config)
}
}
使用 Box::leak
也可以产生 'static
生命周期
#[derive(Debug)]
struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
Some(Box::leak(Box::new(Config {
a: "A".to_string(),
b: "B".to_string(),
})))
}
fn main() {
unsafe {
config = init();
println!("{:?}", config)
}
}
#[derive(Debug)]
struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
Some(Box::leak(Box::new(Config {
a: "A".to_string(),
b: "B".to_string(),
})))
}
fn main() {
unsafe {
config = init();
println!("{:?}", config)
}
}
案例十二
让代码工作
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 print_it2<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
// i 是有所有权的数据,并没有包含任何引用,因此它是 'static
let i = 5;
print_it(i);
// 但是 &i 是一个引用,生命周期受限于作用域,因此它不是 'static
print_it(&i);
print_it1(&i);
// 但是下面的代码可以正常运行 !
print_it2(&i);
}
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 print_it2<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
// i 是有所有权的数据,并没有包含任何引用,因此它是 'static
let i = 5;
print_it(i);
// 但是 &i 是一个引用,生命周期受限于作用域,因此它不是 'static
print_it(&i);
print_it1(&i);
// 但是下面的代码可以正常运行 !
print_it2(&i);
}
use std::fmt::Debug;
fn print_it<T: Debug + 'static>(input: T) {
println!("'static value passed in is: {:?}", input);
}
// 用了不同的书写方式
fn print_it1<T>(input: &T)
where
T: Debug + 'static,
{
println!("'static value passed in is: {:?}", input);
}
fn print_it2<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
// i 是有所有权的数据,并没有包含任何引用,因此它是 'static
let i = 5;
print_it(i);
// 但是 &i 是一个引用,生命周期受限于作用域,因此它不是 'static
print_it(i);
print_it1(&i);
// 但是下面的代码可以正常运行 !
print_it2(&i);
}
use std::fmt::Debug;
fn print_it<T: Debug + 'static>(input: T) {
println!("'static value passed in is: {:?}", input);
}
// 用了不同的书写方式
fn print_it1<T>(input: &T)
where
T: Debug + 'static,
{
println!("'static value passed in is: {:?}", input);
}
fn print_it2<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
// i 是有所有权的数据,并没有包含任何引用,因此它是 'static
let i = 5;
print_it(i);
// 但是 &i 是一个引用,生命周期受限于作用域,因此它不是 'static
print_it(i);
print_it1(&i);
// 但是下面的代码可以正常运行 !
print_it2(&i);
}
案例十三
让代码工作
use std::fmt::Display;
fn main() {
let mut string = "First".to_owned();
string.push_str(string.to_uppercase().as_str());
print_a(&string);
print_b(&string);
print_c(&string); // Compilation error
print_d(&string); // Compilation error
print_e(&string);
print_f(&string);
print_g(&string); // Compilation error
}
fn print_a<T: Display + 'static>(t: &T) {
println!("{}", t);
}
fn print_b<T>(t: &T)
where
T: Display + 'static,
{
println!("{}", t);
}
fn print_c(t: &'static dyn Display) {
println!("{}", t)
}
fn print_d(t: &'static impl Display) {
println!("{}", t)
}
fn print_e(t: &(dyn Display + 'static)) {
println!("{}", t)
}
fn print_f(t: &(impl Display + 'static)) {
println!("{}", t)
}
fn print_g(t: &'static String) {
println!("{}", t);
}
use std::fmt::Display;
fn main() {
let mut string = "First".to_owned();
string.push_str(string.to_uppercase().as_str());
print_a(&string);
print_b(&string);
print_c(&string); // Compilation error
print_d(&string); // Compilation error
print_e(&string);
print_f(&string);
print_g(&string); // Compilation error
}
fn print_a<T: Display + 'static>(t: &T) {
println!("{}", t);
}
fn print_b<T>(t: &T)
where
T: Display + 'static,
{
println!("{}", t);
}
fn print_c(t: &'static dyn Display) {
println!("{}", t)
}
fn print_d(t: &'static impl Display) {
println!("{}", t)
}
fn print_e(t: &(dyn Display + 'static)) {
println!("{}", t)
}
fn print_f(t: &(impl Display + 'static)) {
println!("{}", t)
}
fn print_g(t: &'static String) {
println!("{}", t);
}
use std::fmt::Display;
fn main() {
let mut string = "First".to_owned();
string.push_str(string.to_uppercase().as_str());
print_a(&string);
print_b(&string);
print_c(Box::leak(Box::new(string.clone()))); // Compilation error
print_d(Box::leak(Box::new(string.clone()))); // Compilation error
print_e(&string);
print_f(&string);
print_g(Box::leak(Box::new(string.clone()))); // Compilation error
}
fn print_a<T: Display + 'static>(t: &T) {
println!("{}", t);
}
fn print_b<T>(t: &T)
where
T: Display + 'static,
{
println!("{}", t);
}
fn print_c(t: &'static dyn Display) {
println!("{}", t)
}
fn print_d(t: &'static impl Display) {
println!("{}", t)
}
fn print_e(t: &(dyn Display + 'static)) {
println!("{}", t)
}
fn print_f(t: &(impl Display + 'static)) {
println!("{}", t)
}
fn print_g(t: &'static String) {
println!("{}", t);
}
use std::fmt::Display;
fn main() {
let mut string = "First".to_owned();
string.push_str(string.to_uppercase().as_str());
print_a(&string);
print_b(&string);
print_c(Box::leak(Box::new(string.clone()))); // Compilation error
print_d(Box::leak(Box::new(string.clone()))); // Compilation error
print_e(&string);
print_f(&string);
print_g(Box::leak(Box::new(string.clone()))); // Compilation error
}
fn print_a<T: Display + 'static>(t: &T) {
println!("{}", t);
}
fn print_b<T>(t: &T)
where
T: Display + 'static,
{
println!("{}", t);
}
fn print_c(t: &'static dyn Display) {
println!("{}", t)
}
fn print_d(t: &'static impl Display) {
println!("{}", t)
}
fn print_e(t: &(dyn Display + 'static)) {
println!("{}", t)
}
fn print_f(t: &(impl Display + 'static)) {
println!("{}", t)
}
fn print_g(t: &'static String) {
println!("{}", t);
}