So, to spell things out explicitly: The idea is that if there was a panic between the lines
foo = Foo(&s);
and
drop(foo);
then all initialized local variables would be dropped in usual drop order, which is reverse order of initialization declaration.
This would mean that first s
would be dropped, and then foo
would be dropped. However, the destructor of Foo
that would be run (after the String
is already gone!) has access to the String
field via the &mut self
argument.
This has nothing to do with generic type arguments. Edit: Sorry if this statement caused confusion, see further discussion below.
For further illustration: if we were to circumvent the borrow checker and also insert a panic at the right place, as well as making the Drop
impl actually access the String
, we get really well-observable UB.
first without the panic, not quite UB yet
struct Foo<'a>(&'a String);
impl<'a> Drop for Foo<'a> {
fn drop(&mut self) {
println!("dropping Foo: {}", *self.0);
}
}
fn circumvent_the_borrow_checker<'a, 'b, T>(r: &'a T) -> &'b T {
unsafe {
&*(r as *const T)
}
}
fn main() {
let foo;
let s = String::from("123");
foo = Foo(circumvent_the_borrow_checker(&s));
// panic!();
drop(foo);
}
output:
dropping Foo: 123
with the panic
struct Foo<'a>(&'a String);
impl<'a> Drop for Foo<'a> {
fn drop(&mut self) {
println!("dropping Foo: {}", *self.0);
}
}
fn circumvent_the_borrow_checker<'a, 'b, T>(r: &'a T) -> &'b T {
unsafe {
&*(r as *const T)
}
}
fn main() {
let foo;
let s = String::from("123");
foo = Foo(circumvent_the_borrow_checker(&s));
panic!();
drop(foo);
}
output on current stable compiler, tested in playground, still looks somewhat harmless, but already questionable:
dropping Foo:
but actually it’s undefined behavior:
error: Undefined Behavior: trying to retag from <3355> for SharedReadOnly permission at alloc1659[0x0], but that tag does not exist in the borrow stack for this location
--> src/main.rs:4:38
|
4 | println!("dropping Foo: {}", *self.0);
| ^^^^^^^
| |
| trying to retag from <3355> for SharedReadOnly permission at alloc1659[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc1659[0x0..0x18]
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <3355> was created by a SharedReadOnly retag at offsets [0x0..0x18]
--> src/main.rs:18:5
|
18 | foo = Foo(circumvent_the_borrow_checker(&s));
| ^^^
help: <3355> was later invalidated at offsets [0x0..0x18] by a Unique retag
--> src/main.rs:21:1
|
21 | }
| ^
= note: BACKTRACE (of the first span):
= note: inside `<Foo<'_> as std::ops::Drop>::drop` at src/main.rs:4:38: 4:45
= note: inside `std::ptr::drop_in_place::<Foo<'_>> - shim(Some(Foo<'_>))` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:490:1: 490:56
note: inside `main`
--> src/main.rs:21:1
|
21 | }
| ^
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
And if a longer string than "123"
is used instead, we can even see visible garbage data coming from the allocator’s internals.
// ……
let s = String::from("Hello World, this string is longer!");
// ……
output (varies):
dropping Foo: ��9)\Us string is longer!