Thanks for your reply!
I'll try to make the use case more clear.
I have two instances with totally unrelated lifetimes. However for the duration of the callback I know that both instances are present. So during the callback it is totally fine if both instances interact with each other.
But I need to make sure that both instances do not permanently contaminate each other during this callback.
I can achieve this by coercing the lifetimes of such an interaction (which might be called inside of this callback:
fn interact<'cb>(&'cb a : Instance1, &'cb b : Instance2) -> &'cb InteractionResult;
Here b
could be stored in a
(by interior mutability in a
). To enforce that b
is removed from a
before the callback ends, it is possible to coerce the lifetime of the instances to the lifetime of the callback. Extending the example from my previous post would give us something like this:
use core::ffi::c_void;
struct Instance;
struct OtherInstance;
struct InteractionResult;
fn coerce_lifetime<'g, 't, G, T>(_giver : &'g G, taker : &'t T) -> &'g T
where 't : 'g
{
taker
}
struct Interaction<'a> {
func : for <'b> fn(&'b Instance, &'b OtherInstance) -> &'b InteractionResult,
data : &'a Instance
}
fn interact<'cb>(_a : &'cb Instance, _b : &'cb OtherInstance) -> &'cb InteractionResult {
&InteractionResult
}
unsafe extern "C" fn callback(interaction : *mut c_void, other_instance : *mut c_void) {
// casting the C pointers back to the Rust types
let interaction = interaction.cast::<Interaction>().as_ref().unwrap();
let other_instance = other_instance.cast::<OtherInstance>().as_ref().unwrap();
// creating a variable with a lifetime
let lifetime = ();
// coercing the lifetime of both instances to the lifetime of the created variable
let data = coerce_lifetime(&lifetime, interaction.data);
let other_instance = coerce_lifetime(&lifetime, other_instance);
// calling the interaction function pointer
(interaction.func)(data, other_instance);
}
fn main() {
let my_data = Instance;
let mut interaction = Interaction { func : interact, data : &my_data};
let cb = callback;
let _interaction_ptr : *mut Interaction = &mut interaction;
let _cb_ptr : *mut Option<unsafe extern "C" fn(*mut c_void, *mut c_void)> = &mut Some(cb);
// unsafe { let_it_be_called_from_C(interaction_ptr as *mut c_void, cb_ptr as *mut c_void) }
}
However for this to work as you pointed out it is necessary that Instance
and OtherInstance
are fully concrete types, meaning they are not generic over some T
that might have internal borrows.
After some further investigation I think that the following might work (you also mentioned something like this):
- Creating an unsafe trait that expresses that a types lifetime parameters can be coerced
- Implementing this trait for primitive types
- making this trait derivable for a type if and only if all of its members also implement it (meaning all of its lifetime parameters must be known as well); it must also have no 'static references internally
unsafe trait LifetimeCoercible<'g> {
type Coerced;
fn coerce_lifetime<G>(self, _giver : &'g G) -> Self::Coerced;
}
unsafe impl<'a, 'g, T> LifetimeCoercible<'g> for &'a T
where
'a : 'g,
T : LifetimeCoercible<'g>
{
type Coerced = &'g T;
fn coerce_lifetime<G>(self, _giver : &'g G) -> Self::Coerced {
self
}
}
Thinking further, something like this could be useful for other ffi contexts as well. The nomicon mentions unbounded lifetimes. It recommends to bound the lifetimes that were created from thin air by using a function like coerce_lifetime
. However for internal borrows there is no generic function possible that does this. Bounding of internal borrows is only possible with a fully concrete type.