Sure, Iāll try to unravel all the abstraction and macro mess going on inside rental to make it more clear where the problem comes in.
At the most basic level, rental generates 2 element structs that store an owner and a borrower side by side. One example might be this:
struct RentalStruct {
owner: RefCell<i32>,
borrower: Ref<'???, i32>, // What to put here?
}
In its current state, there is no lifetime that we can place here that will truly express the fact that this field is tied to the lifetime of the other field. For this reason, weāre forced to choose a stand-in, because we still need to store the field somehow.
Could we perhaps instead just use a raw memory chunk and transmute it? Theoretically perhaps, but that would require being able to statically determine the size of Ref and declaring a field with a u8 array exactly that size. As far as I know, you canāt currently do this. Even if you could however, we still have a problem, because to get the size of a type you still need to be able to name it, so we still need to be able to put a valid lifetime there.
So, proceeding with the stand-in lifetime approach, what do we put there? 'static seems perfectly reasonable, and in this case that will work fine. Weāll just need to be careful to remember that itās not REALLY 'static.
Now letās consider another example:
struct RentalStruct<'a> {
owner: RefCell<MyType<'a>>,
borrower: Ref<'static, MyType<'a>>, // Problem, 'a is not 'static, this type can't exist
}
Here, even 'static fails us, since the compiler will not accept it, even as an internal field that will never be publicly exposed. Luckily, we still have a backup lifetime in the form of 'a, and that will save us once again in this case.
Now, what if we want to be generic?
struct RentalStruct<T> {
owner: RefCell<T>,
borrower: Ref<'a, T>, // Problem, 'a is now unknown to us, so we're stuck again
}
Here, T is not bounded to be 'static and could have any lifetime. Unfortunately, we have no idea what that lifetime actually is, so thereās nothing we can put there that will satisfy the compiler, so we must resort to this:
struct RentalStruct<'redundant, T: 'redundant> {
owner: RefCell<T>,
borrower: Ref<'redundant, T>, // Compiler is happy again
}
Now the compiler is satisfied, but weāve paid a price. Or rather, we passed that cost along to the consumer of our API and THEY must pay the price of supplying this redundant lifetime that has no actual meaning other than to satisfy the compiler. In some cases inference will eliminate it for us, but that wonāt always work. If we want to put RentalStruct in a struct of our own, then that struct will also be infected with the redundant lifetime, and so on.
'unsafe just tells the compiler to accept that there is no meaningful lifetime we can actually give it and that we promise to handle the implications properly ourselves. The user of the API is then unaware that this has taken place and sees only a struct that takes a single type parameter, as we wanted in the first place. It also more clearly indicates to any reader of the code that the lifetime of this value is deliberately unsafe and must be handled with extreme care, instead of a lie that we have to remember.