To add on to that: If you were using only safe Rust code, to have a struct with references in it you would have to put a lifetime parameter on the struct itself:
struct Foo<'a> {
bar: &'a u32,
}
You probably want to simulate that in your bindings. But it's not enough to just add a lifetime parameter; you still have to worry about variance. With the above example struct, the following code still works:
fn call<'a, 'b: 'a>(a: &Foo<'a>, b: &'b u32) {}
fn main() {
let a = Foo { bar: &3 };
{
let b = 2;
call(&a, &b);
}
}
Why? Well, based on the type signatures plus the list of fields of in Foo
- but not the bodies of any functions - you can determine that no safe-code implementation of call
can do anything invalid. And unsafe code is expected to uphold the same rules.
If you change the first parameter of call
to have type &mut Foo<'a>
, then a safe implementation of call
could store the second parameter in its field, so you get a lifetime error (even if the body of call
is still blank). (Technically, this is because &mut T
is invariant in T
, while &T
is covariant.)
If you keep the first parameter as &Foo<'a>
, but change Foo
to store a RefCell<&'a u32>
, then safe code could again store the second parameter in the field, through interior mutability, so again you get a lifetime error. (Technically, this is because the lifetime parameter of Foo
is inferred to be invariant.)
For the nitty-gritty on how this works, see Subtyping and Variance in the Rustonomicon.
TL;DR, though, is that you probably want to use PhantomData to make the lifetime parameter on your self type either covariant or invariant, depending on whether you want the method that saves the pointer to take &mut self
or &self
. PhantomData<T>
is a type that pretends to store T
, for variance purposes, but actually stores nothing. Please see the chapter about PhantomData for more on that.
edit: clarified circumstances under which PhantomData should be used