meanwhile I think I understand the "trick" the dynosaur crate does to enable dyn compatibility for traits with async fns but there's still one thing left I don't understand...
Consider this code:
#[dynosaur::dynosaur(DynTest)]
trait Test {
async fn doIt(&self) -> String;
}
What I don't understand is the second assignment to ret here:
impl<'dynosaur_struct> Test for DynTest<'dynosaur_struct> {
fn doIt(&self) -> impl ::core::future::Future<Output = String> {
let ret: ::core::pin::Pin<Box<dyn ::core::future::Future<Output = String> + '_>> =
self.ptr.doIt();
let ret: ::core::pin::Pin<Box<dyn ::core::future::Future<Output = String> + 'static>> =
unsafe { ::core::mem::transmute(ret) };
ret
}
}
I know that the authors of the crate are real Rust pros therefore my assumption is that it is sound here to extend the lifetime of the inner trait object to 'static by using transmute... but why?
Safety: whether it's safe to execute some code (means: would it work, would it trigger UB, etc).
Soundness: whether it's safe to call something from arbitrary safe code.
Note that for #2 to even make some sense there should be a way to call said piece of code from third-party code!
But here that code is not called from some third-part-code, it may only be called from that particular function – and not from any other piece of code. That means that it couldn't be “unsound”, not even in principle.
Now, is it safe? That would depend on wider context: lifetime is not part of the type at runtime which means that conversion from one type to another, when the only difference between types is a lifetime, is always safe. Lifetime doesn't change layout of the type, it doesn't change anything, at runtime, it only exist at compile time. And since we immediately convert that type, once more, into a type that is not having 'self lifetime… no one may ever exploit that temporary variable with a 'static lifetime.
Then the boxed trait object which gets returned contains a state machine which contains in turn references with lifetime 'b, therefore I still don't really understand how this can be ok here... Might it be that the compiler implies here 'b: 'static?
Before we may discuss things you would need to explain what do you mean by that phrase, first.
What exactly is the problem? It sounds to me as if you are imagining lifetimes as something that actually exists in compiled code, as part of working program. And they, accordingly, may be part “references that are created or returned”.
That's not true. Lifetimes are compile-time construct. They are used to prove things about your code – and then they removed. Runtime rules don't rely on lifetimes.
E.g. there rule that there shouldn't be simultaneous active exclusive mutable references and shared immutable references. That's something that's extremely important. And lifetimes are used to ensure that such situation couldn't happen.
But if your program violates the rules for theorem prover – then it doesn't matter as long as actual rules are not violated. As long as all references, in runtime, point to valid objects, don't lead to situation where you have mutable and immutable references, simultaneously, etc.
Only 'static may outlive 'static thus such implication ensure that 'b is 'static. Which would make the whole crate pointless.
That place knowingly and explicitly lies to the compiler (that's why unsafe is there). But as long as that code only violates rules of theorem prover (that's what “borrow checker” is in reality), but doesn't actually violate fundamental semantic rules – program is valid.
P.S. The whole dance is centered around well-known theorem: it's not possible, in general, to validate or prove anything non-trivial about behavior of any non-trivial program. Not as “we may do that, but this would be costly”, no. Impossible as in: no matter what we do compiler couldn't distinguish “good” programs from “bad programs”. And that's… a bit sad, but math works like this. And that's why Rust splits code in two parts: one where compiler proves that your code is “good” and one where developer proves that. And lifetime markup is part of that subsystem that allows compiler to prove that your program is “good”.
No. dyn Trait<'b> + 'static implies the erased type satisfied 'static, not that the parameters like 'b satsify 'static. But because the type is erased, the compiler doesn't have a way to enforce this, and you can use unsafe to turn dyn Trait<'b> + 'not_static into dyn Trait<'b> + 'static, even if the erased type doesn't actually meet a 'static bound.
Of course, if you do so, you have to somehow ensure it's not actually used after it becomes invalid (or suffer UB).
Along those lines, note what actually gets returned in this code...