4

I'm wrapping a C library, and it has a standard sort of context object:

library_context* context = library_create_context();

And then using that you can create more objects:

library_object* object = library_create_object(context);

And destroy them both:

library_destroy_object(object);
library_destroy_context(context);

So I've wrapped this up in Rust structs:

struct Context {
    raw_context: *mut library_context,
}

impl Context {
    fn new() -> Context {
        Context {
            raw_context: unsafe { library_create_context() },
        }
    }

    fn create_object(&mut self) -> Object {
        Object {
            raw_object: unsafe { library_create_object(self.raw_context) },
        }
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        unsafe {
            library_context_destroy(self.raw_context);
        }
    }
}

struct Object {
    raw_object: *mut library_object,
}

impl Drop for Object {
    fn drop(&mut self) {
        unsafe {
            library_object_destroy(self.raw_object);
        }
    }
}

So now I can do this, and it seems to work:

fn main() {
    let mut ctx = Context::new();
    let ob = ctx.create_object();
}

However, I can also do this:

fn main() {
    let mut ctx = Context::new();
    let ob = ctx.create_object();
    drop(ctx);

    do_something_with(ob);
}

I.e. the library context is destroyed before the objects it creates are.

Can I somehow use Rust's lifetime system to prevent the above code from compiling?

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 1
    Please produce a [MCVE] when asking questions. The code as presented fails with 7 errors of undefined items. It's also possible you can make it more **M** while making it **C**. – Shepmaster Dec 13 '16 at 21:10

1 Answers1

6

Yes, just use normal lifetimes:

#[derive(Debug)]
struct Context(u8);

impl Context {
    fn new() -> Context {
        Context(0)
    }

    fn create_object(&mut self) -> Object {
        Object {
            context: self,
            raw_object: 1,
        }
    }
}

#[derive(Debug)]
struct Object<'a> {
    context: &'a Context,
    raw_object: u8,
}

fn main() {
    let mut ctx = Context::new();
    let ob = ctx.create_object();
    drop(ctx);

    println!("{:?}", ob);
}

This will fail with

error[E0505]: cannot move out of `ctx` because it is borrowed
  --> src/main.rs:26:10
   |
25 |     let ob = ctx.create_object();
   |              --- borrow of `ctx` occurs here
26 |     drop(ctx);
   |          ^^^ move out of `ctx` occurs here

Sometimes people like to use PhantomData, but I'm not sure I see the benefit here:

fn create_object(&mut self) -> Object {
    Object {
        marker: PhantomData,
        raw_object: 1,
    }
}

#[derive(Debug)]
struct Object<'a> {
    marker: PhantomData<&'a ()>,
    raw_object: u8,
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Aha I tried that but got it slightly wrong! Thanks! – Timmmm Dec 13 '16 at 21:17
  • @Timmmm note that the choice of `&mut self` for `create_object` means that you can't create a second object though, as it looks like `Context` is still mutably borrowed and/or you couldn't mutate it a second time as there's still a immutable borrow to it; not sure if that's going to mesh with your expectations. – Shepmaster Dec 13 '16 at 21:20
  • Ah that explains the second problem I was having. Is there a way to remove the `mut`-ness while still having the function take `&mut self`? Something like `context: self as &Context,`? – Timmmm Dec 13 '16 at 21:30
  • @Timmmm it depends a lot on what the underlying code does... Does the `Object` actually have a pointer to something in the `Context`? What happens if the `Context` is moved? What about multithreaded concerns - does the underlying code handle that? Why do you have it as `&mut self` to start with? – Shepmaster Dec 13 '16 at 21:36
  • `Object` doesn't have an explicit pointer to `Context` but there might be an internal one (haven't checked yet). If `Context` is moved everything is fine because I haven't derived `Clone` or `Copy`. And raw pointers aren't `Send` so nothing can be moved across threads. At least that is my newbie-level understanding. – Timmmm Dec 14 '16 at 10:00