No it couldn’t just keep around the value in global storage or other threads for longer than its call duration. It only could do that it it had – say – a T: 'static bound, which would then in turn be something the borrow checker would be aware of.
Or if it had a T: 'a bound with some lifetime 'a that appears in a different function argument, it could somehow stash the value into data related to that other parameter. Etc…
Of course it doesn’t have to drop the value, but it has to either drop it or leak it, it becomes inaccessible either way, which is all the borrow checker would need to care about.
struct Foo<'a>(&'a String);
fn main() {
let foo;
let s = String::from("123");
foo = Foo(&s);
// panic!(); the compiler assume the panic
drop(foo);
}
The destruction order at the panic point is still first s and then foo, which is equivalent to the original example, however, this case can be compiled.
That's because without an explicit Drop impl that had access to the reference, a type such as Foo<'a> is allowed to be (implicitly) dropped even when the contained reference is already dead. In particular, your code will still work without the drop(foo) or even with an explicit drop(s).
There's even special (unstable and unsafe) options that allow certain standard library types with an explicit drop to do the same. E. g. Vec<Foo<'a>> will still work if Foo<'a> does even though the Vec does some custom operations (e. g. memory deallocation) when dropped.
That means if we explicitly implement Drop for a struct type, the compiler will assume we will access all fields of the struct type even if we don't do this. If we don't explicitly implement Drop, the compiler can ensure that we cannot access these fields when the container object is being destroyed. So, whether these fields are killed or alive at the destruction of containing object, they don't matter.
The rationale is that the function body of Drop functions should not matter for how the type can or cannot be used, hence the conservative assumption that all fields could potentially be accessed in the Drop impl.
In other words, changing the code in the Drop implementation of your types will never break compilation for users of your type, whereas adding a new Drop implementation can be a breaking change.