学习Rust的第18天:Lifetime

在本文中探讨了Rust生命周期中的关键概念,包括基础知识、借用检查器、通用生命周期注释、生命周期省略规则、结构中的注释和静态生命周期。了解生命周期对于编写安全的Rust代码至关重要,因为它们可以确保内存安全并防止数据竞争。

AI generated image AI生成图像

Introduction 介绍

We’ll study this with an example, because I find it kind of difficult the other way…
我们会用一个例子来研究这个问题,因为我发现用另一种方法来研究有点困难...

fn main(){
  let x: u32 = 20; // lifetime of x starts here
  {
    let y: u32 = 10; //lifetime of y starts here

  } // Y goes out of scope

} // X goes out of scope

The lifetimes of x and are scoped within the blocks where they are declared. has a lifetime that begins with its declaration and ends at the conclusion of the main function. Similarly, y’s lifespan begins with its declaration and ends with the closing curly brace of its block. Rust’s ownership model ensures that memory associated with y is deallocated when it exits scope, and similarly for x. Rust forbids the usage of variables over their intended lives, guaranteeing memory safety.
x 和 的生存期是在声明它们的块中定义的。 的生命周期从它的声明开始,到main函数的结束。类似地,y的寿命从它的声明开始,到它的块的右花括号结束。Rust的所有权模型确保与y关联的内存在退出作用域时被释放,x也是如此。Rust禁止在变量的预期寿命内使用变量,以保证内存安全。

The borrow checker 借用检查器

Rust has a borrow checker which prevents any dangling references from existing
Rust有一个借用检查器,可以防止任何悬空引用的存在。

What is a dangling reference?
什么是dangling reference?

fn main(){
  let x: u32 = 20;
  {
    let y: u32 = 10; 
    let x = &y;
  } 
  println!("{}",x);
} 

This code will not compile because, X is a dangling reference. It does not point to anything because y has already gone out of scope.
这段代码将无法编译,因为X是一个悬空引用。它没有指向任何东西,因为 y 已经超出范围。

Rust tackles this by using the borrow checker in the compile-time. The Rust borrow checker ensures at compile time that references are used safely to prevent data races and memory errors.
Rust通过在编译时使用借用检查器来解决这个问题。Rust借用检查器确保在编译时安全地使用引用,以防止数据竞争和内存错误。

Generic lifetime annotations
通用生存期注释

Generic lifetime annotations in Rust allow functions to accept arguments with any lifetime, providing flexibility in handling references with different lifetimes.
Rust中的泛型生命周期注释允许函数接受具有任何生命周期的参数,从而在处理具有不同生命周期的引用时提供灵活性。

  • Generic lifetime annotations in Rust are denoted by 'a'b, or any other valid lifetime identifier.
    Rust中的通用生命周期注释由 'a 、 'b 或任何其他有效的生命周期标识符表示。
  • These annotations specify that a function or struct can accept references with any lifetime.
    这些注释指定函数或结构可以接受具有任何生存期的引用。
  • The lifetime annotation is typically used to indicate that the lifetime of the returned reference will be the same as the shortest-lived input reference.
    生命周期注释通常用于指示返回引用的生命周期将与最短生命周期的输入引用相同。
  • Generic lifetime annotations provide flexibility in handling references with different lifetimes within the same function or data structure.
    泛型生存期批注提供了处理同一函数或数据结构中具有不同生存期的引用的灵活性。
  • They ensure memory safety by enforcing that references used within the function or struct are valid for the entire duration of their respective lifetimes.
    它们通过强制函数或结构中使用的引用在其各自的生存期的整个持续时间内有效来确保内存安全。
  • In functions, generic lifetime annotations are placed before the parameter list, indicating that the function can accept references with any valid lifetime.
    在函数中,泛型生存期注释放在参数列表之前,指示函数可以接受具有任何有效生存期的引用。
  • The lifetime of the returned reference is inferred based on the lifetimes of the input references and the specified generic lifetime annotation.
    返回引用的生存期是根据输入引用的生存期和指定的泛型生存期注释推断的。

Example 例如

fn main() {
    let string1 = String::from("hello");
    let string2 = String::from("world");

    let result;
    {
        let string3 = String::from("from Rust!");

        result = longest_string(&string1, &string2, &string3);
    }

    println!("The longest string is: {}", result);
}

fn longest_string<'a>(s1: &'a str, s2: &'a str, s3: &'a str) -> &'a str {
    if s1.len() >= s2.len() && s1.len() >= s3.len() {
        s1
    } else if s2.len() >= s1.len() && s2.len() >= s3.len() {
        s2
    } else {
        s3
    }
}
  • The main function is the entry point of the program.
    main 函数是程序的入口点。
  • Inside main, three String variables (string1string2, and string3) are created and initialized with different string values.
    在 main 中,创建了三个 String 变量( string1 、 string2 和 string3 ),并使用不同的字符串值进行初始化。
  • A mutable variable result is declared, which will later hold the result of the longest_string function.
    声明了一个可变变量 result ,它稍后将保存 longest_string 函数的结果。
  • Inside a new scope delimited by curly braces {}, another String variable string3 is created and initialized with the value "from Rust!".
    在由花括号 {} 分隔的新范围内,创建了另一个 String 变量 string3 ,并使用值 "from Rust!" 进行初始化。
  • The longest_string function is called with references to string1string2, and string3, passing them as arguments.
    使用对 string1 、 string2 和 string3 的引用调用 longest_string 函数,并将它们作为参数传递。
  • The longest_string function takes three references to strings (&str) and returns a reference to the longest string among them. It uses a generic lifetime annotation 'a to specify that all input references and the returned reference must have the same lifetime.
    longest_string 函数接受三个对字符串的引用( &str ),并返回其中最长字符串的引用。它使用一个通用的生存期注释 'a 来指定所有输入引用和返回引用必须具有相同的生存期。
  • Within longest_string, it compares the lengths of the input strings (s1s2, and s3) and returns a reference to the longest one.
    在 longest_string 中,它比较输入字符串( s1 、 s2 和 s3 )的长度,并返回对最长字符串的引用。
  • Back in main, the returned reference from longest_string is assigned to the result variable.
    回到 main ,从 longest_string 返回的引用被分配给 result 变量。
  • Finally, the println! macro prints the result, which is the longest string among string1string2, and string3, determined by their lengths.
    最后, println! 宏打印结果,这是 string1 、 string2 和 string3 中最长的字符串,由它们的长度决定。

Output 输出

The longest string is: from Rust!

Let’s look at some error prone code to understand lifetimes better
让我们看看一些容易出错的代码,以便更好地理解生存期

fn main(){
  let x = result();
  println!("{}", x);
}

fn result<'a>() -> &'a str {
  let y = String::from("Hello");
  y.as_str()
}
  • The main function tries to call the result function and store the returned value in variable x.
    main 函数尝试调用 result 函数并将返回值存储在变量 x 中。
  • result function attempts to return a reference to a string slice (&str) created from a String (y).
    result 函数试图返回一个引用到一个从 String ( y )创建的字符串切片( &str )。
  • However, y is a locally scoped String variable and will be deallocated at the end of result function, making the reference invalid.
    然而, y 是一个局部作用域的 String 变量,将在 result 函数结束时被释放,使引用无效。
  • This results in a lifetime error because the function attempts to return a reference to data that goes out of scope at the end of the function.
    这将导致生存期错误,因为函数试图返回对在函数结束时超出范围的数据的引用。
y.as_str()

returns a value referencing data owned by the current function
`y` is borrowed here

To fix this, we can return an owned string, instead of a reference to a string slice by doing this
为了解决这个问题,我们可以返回一个拥有的字符串,而不是这样做的字符串切片的引用

fn main(){
    let x = result();
    println!("{}", x);
}

fn result() -> String {
    let y = String::from("Hello");
    y
}

Lifetime annotations in structs
结构中的生存期注释

Lifetime annotations in structs allow specifying lifetimes for references within struct fields, ensuring that they adhere to Rust’s ownership rules.
结构中的生存期注释允许在结构字段中指定引用的生存期,确保它们遵守Rust的所有权规则。

We use the same syntax to specify lifetimes for structs…
我们使用相同的语法来指定结构的生存期.

struct MyStruct<'a> {
    data: &'a str,
}

impl<'a> MyStruct<'a> {
    fn new(data: &'a str) -> Self {
        MyStruct { data }
    }

    fn print_data(&self) {
        println!("{}", self.data);
    }
}

fn main() {
    let data = String::from("Hello, world!");
    {
        let my_struct = MyStruct::new(&data);
        my_struct.print_data();
    } // my_struct goes out of scope

    // Here, `data` is still valid because its scope extends beyond `my_struct`
    println!("Outside the scope: {}", data);
}
  • In the MyStruct definition, 'a is a lifetime annotation indicating that data field will contain a reference (&str) with the same lifetime 'a.
    在 MyStruct 定义中, 'a 是一个生命周期注释,指示 data 字段将包含具有相同生命周期 'a 的引用( &str )。
  • The MyStruct implementation includes a constructor new that takes a reference to a string slice with the same lifetime as the struct.
    MyStruct 实现包括一个构造函数 new ,它引用一个与结构体具有相同生命周期的字符串切片。
  • The print_data method prints the data stored in the struct.
    print_data 方法打印存储在结构中的数据。
  • In main, a String instance data is created and passed as a reference to MyStruct::new.
    在 main 中,创建了一个 String 实例 data ,并将其作为对 MyStruct::new 的引用传递。
  • my_struct is then printed and goes out of scope, but data remains valid as its lifetime extends beyond the scope of my_struct.
    然后打印出 my_struct 并超出范围,但 data 仍然有效,因为其生存期超出了 my_struct 的范围。

Lifetime Elision Rules 终身省略规则

Lifetime elision rules in Rust are a set of implicit guidelines followed by the compiler to infer lifetimes for references in function signatures. These rules allow us to write code without explicitly annotating lifetimes in many common scenarios, reducing verbosity and improving code readability.
Rust中的生存期省略规则是编译器遵循的一组隐式准则,用于推断函数签名中引用的生存期。这些规则允许我们在许多常见场景中编写代码,而无需显式地注释生存期,从而减少冗长并提高代码可读性。

To understand lifetime elision rules, we first need to understand Input and Output lifetimes
要理解生存期省略规则,我们首先需要理解输入和输出生存期

Input lifetimes: 输入寿命:

  • Input lifetimes refer to the lifetimes associated with references passed as parameters to a function or method.
    输入生存期是指与作为参数传递给函数或方法的引用相关联的生存期。
  • They determine how long the references passed into the function must remain valid.
    它们决定了传递到函数中的引用必须保持有效的时间。
  • In Rust’s lifetime elision rules, input lifetimes are used to infer the lifetimes of references returned from the function.
    在Rust的生存期省略规则中,输入生存期用于推断从函数返回的引用的生存期。

Output lifetimes: 输出寿命:

  • Output lifetimes refer to the lifetime associated with references returned from a function or method.
    输出生存期是指与从函数或方法返回的引用相关联的生存期。
  • They specify how long the returned references remain valid after the function or method call.
    它们指定在函数或方法调用之后返回的引用保持有效的时间。
  • In Rust’s lifetime elision rules, the output lifetime is inferred based on the input lifetimes and the structure of the function or method’s parameters.
    在Rust的生命周期省略规则中,输出生命周期是根据输入生命周期和函数或方法参数的结构来推断的。

Rules 规则

Single input: 单输入:

  • When a function or method has only one input lifetime parameter (such as &self or a single reference parameter), Rust automatically applies that single lifetime to all output lifetimes.
    当一个函数或方法只有一个输入生命周期参数(例如 &self 或单个引用参数)时,Rust会自动将该生命周期应用于所有输出生命周期。
  • This simplifies the code by avoiding the need for redundant annotations.
    这通过避免冗余注释的需要简化了代码。

Example : 范例:

fn foo<'a>(x: &'a str) -> &'a str { ... }

can be changed to 可以更改为

fn foo(x: &str) -> &str { ... }

Multiple inputs: 多个输入:

  • If there are multiple input lifetime parameters, Rust applies the first two rules independently to each parameter to determine their lifetimes.
    如果有多个输入生命周期参数,Rust将前两个规则独立地应用于每个参数,以确定它们的生命周期。
  • This allows the compiler to infer lifetimes separately for each reference, ensuring correctness.
    这允许编译器为每个引用分别推断生存期,以确保正确性。

Example : 范例:

fn bar<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { ... }

can be changed to 可以更改为

fn bar(x: &str, y: &str) -> &str { ... }

Output inferred: 推断输出:

  • When there’s exactly one reference type among the parameters (excluding &self or &mut self in methods), Rust uses the lifetime of that reference as the output lifetime.
    当参数中只有一个引用类型时(不包括方法中的 &self 或 &mut self ),Rust使用该引用的生命周期作为输出生命周期。
  • This rule is especially useful for functions where one of the input references dictates the lifetime of the output reference, making the code more concise.
    这条规则对于其中一个输入引用决定输出引用的生存期的函数特别有用,使代码更加简洁。

Example : 范例:

fn baz<'a>(x: &'a str, y: &str) -> &'a str { ... }

can be changed to 可以更改为

fn baz(x: &str, y: &str) -> &str { ... }

Bounds require annotation:
边界需要注释:

  • Lifetime elision rules don’t apply to associated functions or trait methods with lifetime bounds.
    生存期省略规则不适用于具有生存期边界的关联函数或trait方法。
  • In such cases, explicit lifetime annotations are necessary to specify the relationship between the input and output lifetimes accurately.
    在这种情况下,需要显式的生存期注释来准确地指定输入和输出生存期之间的关系。

Static lifetimes 静态寿命

In Rust, 'static is a special lifetime that denotes a reference with a lifetime that lasts for the entire duration of the program's execution. This means that the data being referenced remains valid for the entire lifetime of the program.
在Rust中, 'static 是一个特殊的生命周期,它表示一个引用的生命周期持续到程序执行的整个持续时间。这意味着被引用的数据在程序的整个生存期内都是有效的。

Example : 范例:

fn main() {
    let static_str: &'static str = "I am a static string.";
    
    println!("{}", static_str);
}
  • In this example, static_str is a reference to a string slice (&str) annotated with the 'static lifetime.
    在本例中, static_str 是对使用 'static 生存期注释的字符串切片( &str )的引用。
  • The string slice "I am a static string." is a string literal, which has a static lifetime by default. Therefore, we can assign it to a reference with a 'static lifetime.
    字符串slice "I am a static string." 是一个字符串字面量,默认情况下具有静态生存期。因此,我们可以将其分配给具有 'static 生存期的引用。
  • This reference can be used throughout the entire program’s execution because it points to data that exists for the entire duration of the program.
    这个引用可以在整个程序的执行过程中使用,因为它指向的数据在程序的整个执行过程中都存在。
  • The 'static lifetime is commonly used for global constants, string literals, or data stored in static memory locations, ensuring that they remain valid for the entire program's lifetime without any possibility of being deallocated.
    'static 生存期通常用于全局常量、字符串文字或存储在静态内存位置的数据,以确保它们在整个程序生存期内保持有效,而不可能被释放。

  • 34
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老父亲的能量嘎嘣脆

感谢支持,共同成长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值