4

I'm wrapping a C library that has two structs: one has a pointer to the other.

struct StructA {
    void * some_mem;
};

struct StructB {
    void * some_mem;
    struct StructA * some_struct;
};

Both of these structs own memory, so my wrapper has constructors and destructors for both of them.

struct StructA(*mut c_void);

impl StructA {
    fn new() -> Self {
        StructA(c_constructor())
    }
}

impl Drop for StructA {
    fn drop(&mut self) {
        let StructA(ptr) = self;
        c_destructor(ptr);
    }
}

There's also a function that takes a pointer to StructB and returns its pointer to StructA:

const struct StructA * get_struct(const struct StructB * obj);

The user of this function should not free the returned pointer, since it will be freed when the user frees obj.

How can I wrap this function? The problem is that the destructor for StructB frees all its memory, including the one for StructA. So if my wrapping of get_struct returns an object, then the wrapped StructA will be freed twice (right?). It could instead return a reference to an object, but where would that object live?

I could have separate structs for StructA based on whether it's standalone and needs to be freed or if it's a reference, but I'm hoping that's unnecessary.

2 Answers 2

4

I could have separate structs for StructA based on whether it's standalone and needs to be freed or if it's a reference, but I'm hoping that's unnecessary.

It's necessary. The difference between an owned StructA * and a borrowed StructA * is precisely the same as the difference between a Box<T> and a &T. They're both "just a pointer", but the semantics are completely different.

Something along these lines is probably what you want:

use std::marker::PhantomData;
struct OwnedA(*mut c_void);
impl Drop for OwnedA {
    fn drop(&mut self) { }
}
impl OwnedA {
    fn deref(&self) -> RefA { RefA(self.0, PhantomData) }
}
struct RefA<'a>(*mut c_void, PhantomData<&'a u8>);

struct OwnedB(*mut c_void);

impl Drop for OwnedB {
    fn drop(&mut self) { }
}

impl OwnedB {
    fn get_a(&self) -> RefA { RefA(get_struct(self.0), PhantomData) }
}

In particular, it's worth noting that lifetime parameter on RefA lets the compiler make sure you don't use a RefA after the backing structure has been freed.

Sign up to request clarification or add additional context in comments.

Comments

2

I could have separate structs for StructA based on whether it's standalone and needs to be freed or if it's a reference, but I'm hoping that's unnecessary.

I believe this would be the accepted pattern. For backup, I'd point to the fact that this is a normal pattern in the Rust library. &str and String, &[T] and Vec<T>, Path and PathBuf, and probably lots of others I can't think of.

The good news is that you can use similar patterns as these pairs, leveraging Deref or DerefMut to call down to shared implementation:

use std::ops::{Deref, DerefMut};

enum RawFoo {}

fn c_foo_new() -> *const RawFoo { std::ptr::null() }
fn c_foo_free(_f: *const RawFoo) {}
fn c_foo_count(_f: *const RawFoo) -> u8 { 42 }
fn c_foo_make_awesome(_f: *const RawFoo, _v: bool) { }

struct OwnedFoo(Foo);

impl OwnedFoo {
    fn new() -> OwnedFoo {
        OwnedFoo(Foo(c_foo_new()))
    }
}

impl Drop for OwnedFoo {
    fn drop(&mut self) { c_foo_free((self.0).0) }
}

impl Deref for OwnedFoo {
    type Target = Foo;
    fn deref(&self) -> &Self::Target { &self.0 }
}

impl DerefMut for OwnedFoo {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}

struct Foo(*const RawFoo);

impl Foo {
    fn count(&self) -> u8 { c_foo_count(self.0) }
    fn make_awesome(&mut self, v: bool) { c_foo_make_awesome(self.0, v) }
}

fn main() {
    let mut f = OwnedFoo::new();
    println!("{}", f.count());
    f.make_awesome(true);
}

Then, when you get a borrowed pointer from your other object, just wrap it up in a &Foo:

use std::mem;

fn c_bar_foo_ref() -> *const RawFoo { std::ptr::null() }

// Ignoring boilerplate for wrapping the raw Bar pointer 
struct Bar;

impl Bar {
    fn new() -> Bar { Bar }

    fn foo(&self) -> &Foo {
        unsafe { mem::transmute(c_bar_foo_ref()) }
    }

    fn foo_mut(&mut self) -> &mut Foo {
        unsafe { mem::transmute(c_bar_foo_ref()) }
    }
}

fn main() {
    let mut b = Bar::new();
    println!("{}", b.foo().count());
    b.foo_mut().make_awesome(true);

    // Doesn't work - lifetime constrained to Bar
    // let nope = Bar::new().foo();
}

1 Comment

How can you transmute a *const RawFoo to a &Foo where Foo is struct Foo(*const RawFoo)? Isn't that like trying to transmute a *const RawFoo to a & *const RawFoo? My slightly modified example causes Miri to complain about this: error: Undefined Behavior: constructing invalid value: encountered a dangling reference (going beyond the bounds of its allocation).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.