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.