Hello,
I'm working on a Rust binding for an existing C++ library, and fall into a situation where there is circular reference over objects, as following
struct Foo {
// attach a listener
void attach(Bar *bar) {
/* push bar into a list of listeners */
}
/* other methods */
};
struct Bar {
Bar(Foo *foo) {
if (foo) foo->attach(this);
}
/* other methods */
}
I believe that this implementation is erroneous, because when a listener Bar
is deleted, there is no method to notify Foo
; e.g:
Foo foo();
{
Bar bar(&foo);
}
// bar is deleted here but foo doesn't know that and uses bar
In short, any correct usage requires that foo
and bar
have the same lifetime.
But my job is not to blame the C++
implementation, rather write a binding for that such a situation can be compile-time checked, e.g. some Foo
and Bar
in Rust for which:
let foo = Foo::new();
{
let bar = Bar::new(&mut foo);
}
is refused by the compiler.
So I've done, first a C
wrapper
// a simple C wrapper with type-erasure
extern "C" {
void *foo_create() {
auto *f = new Foo();
return f;
}
void *foo_attach(void* foo, void *bar) {
auto *f = static_cast<Foo*>(foo);
auto *b = static_cast<Bar*>(bar);
f->attach(b);
}
void *bar_create(void *foo) {
auto *f = static_cast<Foo*>(bar);
auto *b = new Bar(f);
return b;
}
then a Rust binding
// Rust binding
pub struct Foo {
obj: NonNull<c_void>; // opaque pointer to a Foo
}
pub struct Bar<'a> {
obj: NonNull<c_void>; // opaque pointer to a Bar
_p: PhantomData<&'a ()>; // store lifetime of some Foo
}
impl Foo {
pub fn new() -> Self {
Foo {
obj: unsafe { NonNull::new_unchecked(foo_create()) },
}
}
pub fn attach(&mut self, bar: &mut Bar) {
unsafe { foo_attach(self.obj, bar.obj); }
}
}
impl<'a> Bar<'a> {
pub fn new(f: &'a mut Foo) {
Bar {
obj: unsafe { NonNull::new_unchecked(bar_create()) },
_p: PhantomData, // store lifetime of f
}
}
}
But this simple binding doesn't work, the compilers only checks that the lifetime of foo
is larger than (or equal to) bar
, but not vice-versa, i.e.
let foo = Foo::new();
{
let bar = Bar::new(&mut foo);
}
still type-check.
Adding a phantom field into Foo
is non-sense, because we cannot predict the lifetime of a possibly attached bar
: only when it's explicitly attached via attach
.
Is there any method which helps check the lifetime of foo
and bar
be identical?