简体   繁体   English

为什么 Rust 中的 &str 数组作为参数传递有不同的生命周期?

[英]Why do &str arrays in Rust passed as parameters have different lifetimes?

I am in the process of learning Rust and was testing some array copying through a function.我正在学习 Rust 并且正在测试通过函数进行的一些数组复制。 I am sure there are built-in Rust functions to copy/clone array information, but a personal implementation I thought would be a good idea to help my understanding of passing references through functions.我确信有内置的 Rust 函数来复制/克隆数组信息,但是我认为个人实现是一个好主意,可以帮助我理解通过函数传递引用。

fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60]) {
    // copy 1 into 2
    for i in 0..60 {
        a2[i] = a1[i];
    } // change is reflected in a2 as it is passed as &mut
}

However, this threw the error these two types are declared with different lifetimes... for the &str types themselves.但是,这引发了错误, these two types are declared with different lifetimes...对于&str类型本身。 After some further studying, I tried declaring my own lifetime and assigning them to it, and that fixed it!经过一些进一步的学习,我尝试声明自己的一生并将它们分配给它,然后就解决了!

fn copy_str_arr_fix<'a> (a1: [&'a str; 60], a2: &mut [&'a str; 60]) {
    // copy 1 into 2
    for i in 0..60 {
        a2[i] = a1[i];
    } // change is reflected in a2 as it is passed as &mut
}

Why is this the case, though?但是,为什么会这样呢? Why does the type of values within the array need to have a lifetime assigned instead of the parameters themselves?为什么数组中的值类型需要分配生命周期而不是参数本身? In other words, why does this not work at all?换句话说,为什么这根本不起作用?

fn copy_str_arr_bad<'a> (a1: &'a [&str; 60], a2: &'a mut [&str; 60]) {
    // does not work...           ^-----------------------^-------- different lifetimes
    for i in 0..60 {
        a2[i] = a1[i]; 
    } 
}

I am still struggling to get the hang of how lifetimes work in the context of more complex objects such as arrays and structs, so any explanation would be greatly appreciated!我仍在努力了解生命周期在更复杂的对象(如数组和结构)的上下文中是如何工作的,因此将不胜感激任何解释!

The error message is a bit confusing because it refers to lifetimes generated as per rules of lifetime elision .错误消息有点令人困惑,因为它指的是根据生命周期省略规则生成的生命周期 In your case, lifetime elision means that:在您的情况下,终身省略意味着:

fn copy_str_arr_original(a1: [&str; 60], a2: &mut [&str; 60])

is syntax sugar for:是语法糖:

fn copy_str_arr_original<'a1, 'a2_mut, 'a2>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60])

In other words, we have three completely unrelated lifetimes.换句话说,我们有三个完全不相关的生命。 "Unrelated" means that the caller gets to choose how long the objects they're associated with live. “不相关”意味着调用者可以选择与他们相关联的对象存活多长时间。 For example, the strings in a2 might be static and live until the end of the program, while the strings in a1 might get dropped immediately after copy_str_arr_original() returns.例如, a2的字符串可能是静态的并且一直存在到程序结束,而a1的字符串可能会在copy_str_arr_original()返回后立即删除。 Or the other way around.或者反过来。 If that seems like a problem to you, you're on the right track because the borrow checker agrees with you.如果这对您来说是个问题,那么您就在正确的轨道上,因为借用检查员同意您的意见。

Note that, somewhat counter-intuitively, the length of the 'a2_mut lifetime is completely irrelevant, it can be as long or as short as the caller likes.请注意,有点违反直觉, 'a2_mut生命周期的长度完全无关,它可以根据调用者的喜好长或短。 Our function has received the reference and can therefore use it during the function's scope.我们的函数已经收到引用,因此可以在函数的作用域内使用它。 'a2_mut lifetime tells us how long it will live outside the scope of the function, and we just don't care about that. 'a2_mut生命周期告诉我们它将在函数范围之外存活多久,我们只是不关心这个。

'a1 and 'a2 are another matter. 'a1'a2是另一回事。 Since we're copying references from a1 to a2 , we are effectively casting the references inside a1 (of type &'a1 str ) to the type of references stored in a2 (which is &'a2 str ):由于我们将引用从a1复制到a2 ,我们有效地a1的引用(类型为&'a1 str )转换为存储在a2的引用类型(即&'a2 str ):

a2[i] = a1[i];  // cast &'a1 str to &'a2 str

For that to be valid, &'a1 str must be a subtype of &'a2 str .为了使其有效, &'a1 str必须是&'a2 str子类型 While Rust doesn't have classes and subclassing in the C++ sense, it does have subtypes where lifetimes are concerned.虽然 Rust 没有 C++ 意义上的类和子类,但它确实有涉及生命周期的子类型。 In that sense, A is a subtype of B if values of A values are guaranteed to live at least as long as values of B. In other words, 'a1 must leave at least as long as 'a2 , which is expressed as 'a1: a2 .从这个意义上说,如果 A 值的值保证至少与 B 的值一样长,则 A 是 B 的子类型。换句话说, 'a1必须至少离开'a2 ,这表示为'a1: a2 So this compiles:所以这编译:

fn copy_str_arr<'a1: 'a2, 'a2, 'a2_mut>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60]) {
    for i in 0..60 {
        a2[i] = a1[i];
    }
}

Another way for the cast to succeed is to just require the lifetime to be the same , which you effectively did in your code.强制转换成功的另一种方法是只要求生命周期相同,您在代码中有效地做到了这一点。 (You also omitted the 'a2_mut lifetime, which compiler correctly interpreted as a request for an unrelated anonymous lifetime.) (您还省略了'a2_mut生命周期,编译器正确地将其解释为对无关匿名生命周期的请求。)

Let's assume that you can define copy_str_arr with two different, unrelated lifetimes, like this:假设您可以使用两个不同的、不相关的生命周期定义copy_str_arr ,如下所示:

fn copy_str_arr<'a, 'b>(a1: [&'a str; 60], a2: &mut [&'b str; 60]) {
    // ...
}

Then consider this example:然后考虑这个例子:

let mut outer: [&str; 60] = [""; 60];

{
    let temp_string = String::from("temporary string");
    
    let inner: [&str; 60] = [&temp_string; 60];

    // this compiles because our bad `copy_str_arr` function allows
    // `inner` and `outer` to have unrelated lifetimes
    copy_str_array(&inner, &mut outer); 

}   // <-- `temp_string` destroyed here

// now `outer` contains references to `temp_string` here, which is invalid
// because it has already been destroyed!

println!("{:?}", outer); // undefined behavior! may print garbage, crash your
                         // program, make your computer catch fire or anything else

As you can see, if a1 and a2 are allowed to have completely unrelated lifetimes, then we can end up in a situation where one of the arrays holds references to invalid data, which is very bad .如您所见,如果允许a1a2具有完全不相关的生命周期,那么我们最终可能会遇到这样一种情况,其中一个数组包含对无效数据的引用,这是非常糟糕的

However, the lifetimes do not have to be the same.但是,寿命不必相同。 You can instead require that the lifetime you are copying from outlives the lifetime you are copying to (thus ensuring that you're not illegally extending the lifetime of a reference):您可以改为要求您正在复制的生命周期比您复制到的生命周期更长(从而确保您不会非法延长引用的生命周期):

fn copy_str_arr<'a, 'b>(a1: &[&'a str; 60], a2: &mut [&'b str; 60])
where
    'a: 'b, // 'a (source) outlives 'b (destination)
{
    for i in 0..60 {
        a2[i] = a1[i];
    }
}

The simple answer is that the compiler is not very smart.简单的答案是编译器不是很聪明。

The fact that you do not have to specify a bunch of lifetimes every time you define a function which handles references is only because the compiler takes a few educated guesses if it can .您不必在每次定义处理引用的函数时都指定一堆生命周期,这仅仅是因为编译器在可能的情况下进行了一些有根据的猜测 So it is a little bit smart, but not very.所以它有点聪明,但不是很聪明。

Say you are writing a function that takes a reference to a struct and returns a reference to a field in that struct:假设您正在编写一个函数,该函数接受对结构的引用并返回对该结构中字段的引用:

struct Book {
  pages: u16,
  title: String,
}

fn borrow_title(book: &Book) -> &str {
  &book.title
}

Nine times out of ten it is indeed a reference to the argument you passed.十分之九它确实是对您传递的参数的引用。 But sometimes it is not:但有时不是:

fn borrow_title(book: &Book) -> &'static str {
  if book.pages > 10 {
    "Too long..."
  } else {
    "Not long enough"
  }
}

As you can see you would need to specify that the returned &str has a different lifetime (in this case the special 'static .如您所见,您需要指定返回的&str具有不同的生命周期(在这种情况下,特殊的'static .

So since you say fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60]) , the compiler does not actually reason about your implementation and does not know that the lifetime of references in a1 should be at least as long as the lifetime of any reference in a2 .所以既然你说fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60]) ,编译器实际上并没有推理你的实现,也不知道a1中引用的生命周期应该至少是只要a2中任何引用的生命周期。

As for the second part, you need to consider that a reference is just a pointer to some data.至于第二部分,您需要考虑引用只是指向某些数据的指针。 That data can contain other references.该数据可以包含其他引用。 In this case it is these other references that are important.在这种情况下,重要的是这些其他参考。

You have 2 arrays of string references here.这里有 2 个字符串引用数组。 Say you copy the references from the first one to the second one.假设您将引用从第一个复制到第二个。 Whether you pass these arrays into the function by reference or not is not important.是否通过引用将这些数组传递给函数并不重要。 What is important is that if whatever holds the ownership of the first array was dropped, the strings would be too.重要的是,如果第一个数组的所有权被删除,字符串也会被删除。 And if the second array still held any references, this would result in unsafe memory handling.如果第二个数组仍然持有任何引用,这将导致不安全的内存处理。

To simplify, let's consider that there is only one string and we are going to borrow values into an array and then copy those borrowed values to another array, drop the first array and then drop the string.为简化起见,让我们考虑只有一个字符串,我们要将值借用到一个数组中,然后将这些借用值复制到另一个数组中,删除第一个数组,然后删除该字符串。 What would you expect to happen?你希望发生什么?

The compiler will throw a fit in order to ensure that no references to the string remain.编译器将抛出一个合适的问题,以确保没有对字符串的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM