简体   繁体   中英

What are the rules for lifetimes in Rust when using boxed trait objects?

The code below doesn't compile with error E0597: borrowed value (two) doesn't live long enough.

fn main() {
    let one = String::from("one");

    let mut _it: Box<dyn Iterator<Item=char>> = Box::new(one.chars());

    let two = String::from("two");

    _it = Box::new(two.chars());
}

But, if instead of boxed trait object I use reference to a trait object it works:

fn main() {
    let one = String::from("one");

    let mut _it: &dyn Iterator<Item=char> = &one.chars();

    let two = String::from("two");

    _it = &two.chars();
}

Also, if I don't use trait object it works too:

fn main() {
    let one = String::from("one");

    let mut _it: Box<_> = Box::new(one.chars());

    let two = String::from("two");

    _it = Box::new(two.chars());
}

Why?

Let's take them one by one.

In your first example, the problem is subtle and hinted at by the error message

8 |     _it = Box::new(two.chars());
  |                    ^^^^^^^^^^^ borrowed value does not live long enough
9 | }
  | -
  | |
  | `two` dropped here while still borrowed
  | borrow might be used here, when `_it` is dropped and runs the destructor for type `Box<dyn Iterator<Item = char>>`
  |
  = note: values in a scope are dropped in the opposite order they are defined

As the note says, values are dropped in the opposite order they are defined. As two is defined in line 6 and _it is defined (not assigned ) in line 4, the compiler will try to destroy two before it tries to destroy _it at the end of the function. But by the time we reach the end of the function, _it holds a reference to two (line 8), and the imposed ordering would destroy two while _it still holds a reference to it. The destructor of _it could observe the destroyed value two , so this is not allowed.

Why is there even a destructor? Because the trait object dyn Iterator... - just like any other trait object - could hold some dynamic type that has a destructor. And that destructor might observe things it potentially references; as far as the compiler knows, that's two . While the actual Chars -type does not suffer from this problem, I could conjure up such a type and stick it into a dyn Iterator... . So with trait objects, the order in which values are dropped is always important.

The solution is to change the order of definition:

fn main() {
    let one = String::from("one");

    let two; // Notice the definition, before `_it` so it gets dropped *after*

    let mut _it: Box<dyn Iterator<Item=char>> = Box::new(one.chars());

    two = String::from("two");

    _it = Box::new(two.chars());
}

In the second example, you are using plain references. Since plain references do not have destructors, there is no destructor that could potentially observe a destroyed value while it is running, and the order in which values are destroyed is not important; so it compiles just fine.

In the third example, there are no trait objects involved, the boxed type is simply a normal type as far as the compiler is concerned (just like Box<u32> ). Because it is a concrete type, the compiler can figure out that the type inside the Box (which is a Chars , and holds a reference of some lifetime), has a trivial destructor (which does nothing), and therefore the Box has a trivial destructor that simply deallocates; so it doesn't matter that two is in fact destroyed before _it , because _it will definitely not be able to observe two in its destructor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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