5

I have Engine which owns Worker and I want Engine to provide some API to Worker as a reference to trait. API implementation is allocated using Box and is owned by Engine, so reference to it is stable and valid as long as worker is alive.

But I don't understand how to express it in Rust.

I have read Why can't I store a value and a reference to that value in the same struct? and I understand why I can't pass reference to owned value. However, in my case, I pass reference not to owned value itself, but to boxed value, which won't be moved, so reference to it must be stable.

Here is non-working prototype:

trait EngineApi {
    fn foo(&self);
}

struct Worker<'a> {
    api: &'a EngineApi,
}
impl<'a> Worker<'a> {
    fn new(engine_api: &'a EngineApi) -> Self {
        Worker { api: engine_api }
    }
}

struct Api;
impl EngineApi for Api {
    fn foo(&self) {} 
}

struct Engine<'a> {
    api: Box<Api>,
    worker: Box<Worker<'a>>,
}

impl<'a> Engine<'a> {
    fn new() -> Self {
        let api = Box::new(Api);
        let worker = Box::new(Worker::new(api.as_ref()));
        Engine { api: api, worker: worker }
    }
}

fn main() {
    let engine = Engine::new();
}

Errors:

test.rs:27:37: 27:40 error: `api` does not live long enough
test.rs:27      let worker = Box::new(Worker::new(api.as_ref()));
                                                  ^~~
test.rs:25:19: 29:3 note: reference must be valid for the lifetime 'a as defined on the block at 25:18...
test.rs:25  fn new() -> Self {
test.rs:26      let api = Box::new(Api);
test.rs:27      let worker = Box::new(Worker::new(api.as_ref()));
test.rs:28      Engine { api: api, worker: worker }
test.rs:29  }
test.rs:26:27: 29:3 note: ...but borrowed value is only valid for the block suffix following statement 0 at 26:26
test.rs:26      let api = Box::new(Api);
test.rs:27      let worker = Box::new(Worker::new(api.as_ref()));
test.rs:28      Engine { api: api, worker: worker }
test.rs:29  }
error: aborting due to previous error
Community
  • 1
  • 1
kriomant
  • 2,216
  • 1
  • 16
  • 21
  • Possible duplicate of [Why can't I store a value and a reference to that value in the same struct?](http://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Matthieu M. Mar 14 '16 at 10:24
  • I have read that question, and I understand why I can't store reference to owned value — because value is moved when stored to struct and later, when struct itself is moved. However, in my case value is boxed, so reference is stable and, while it may be not expressible in Rust, it must be valid. – kriomant Mar 14 '16 at 10:29
  • In this case, could you (1) cite this link in your question and (2) explain in your question what you understood and why you think that your case is different? This will help us understand exactly what to explain. – Matthieu M. Mar 14 '16 at 10:31

1 Answers1

3

The problem is that in your example, there's nothing binding the api object to live longer than the scope it is created in. So basically you'd need to create the entire engine object first, and then Rust could reason about these lifetimes. But you can't create an object safely without filling out all fields. But you can change the worker field to an Option and fill it out later:

struct Engine<'a> {
    api: Box<Api>,
    worker: Option<Box<Worker<'a>>>,
}

impl<'a> Engine<'a> {
    fn new() -> Self {
        let api = Box::new(Api);
        Engine { api: api, worker: None }
    }
    fn turn_on(&'a mut self) {
        self.worker = Some(Box::new(Worker::new(self.api.as_ref())));
    }
}

fn main() {
    let mut engine = Engine::new();
    engine.turn_on();
}

The call to engine.turn_on() will lock the object to ensure it will stay in the scope. You don't even need boxes to ensure safety then, because the object will become immovable:

struct Engine<'a> {
    api: Api,
    worker: Option<Worker<'a>>,
}

impl<'a> Engine<'a> {
    fn new() -> Self {
        let api = Api;
        Engine { api: api, worker: None }
    }
    fn turn_on(&'a mut self) {
        self.worker = Some(Worker::new(&self.api));
    }
}

fn main() {
    let mut engine = Engine::new();
    engine.turn_on();
}

The Rust compiler cannot use the fact that the object should be movable because the things it references are stored on the heap and live at least as long as the object. Maybe some day in the future. For now you have to resort to unsafe code.

oli_obk
  • 28,729
  • 6
  • 82
  • 98
  • WoW. This self-locking is just magic. It took me a bit to understand the difference of behavior between before `turn_on` (where I can move the engine anywhere) and after `turn_on` (where it's immovable) and I'm still not sure I have all the details sorted out. The fact that it's immovable afterward might be painful though (there is no `turn_off`). – Matthieu M. Mar 14 '16 at 12:56
  • 1
    Yeah, this self-locking is really cool trick. However, I don't like two-stage initialization, so I will probably resort to sharing API by Rc. – kriomant Mar 14 '16 at 14:45