Как дать ссылку на принадлежащую коробочную структуру на другую принадлежащую структуру

Я имею Engine который владеет Worker и я хочу Engine предоставить некоторый API для Worker в качестве ссылки на черту. Реализация API выделяется с помощью Box и принадлежит Engine, так что ссылка на него стабильна и действительна, пока работник жив.

Но я не понимаю, как выразить это в Rust.

Я прочитал Почему я не могу сохранить значение и ссылку на это значение в той же структуре? и я понимаю, почему я не могу передать ссылку на собственную стоимость. Однако в моем случае я передаю ссылку не на собственное значение, а на коробочное значение, которое не будет перемещено, поэтому ссылка на него должна быть стабильной.

Вот нерабочий прототип:

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();
}

Ошибки:

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

1 ответ

Решение

Проблема в том, что в вашем примере нет ничего связывающего api объект должен жить дольше, чем область, в которой он создан. Поэтому в основном вам нужно сначала создать весь объект движка, а затем Rust может подумать об этих временах жизни. Но вы не можете создать объект безопасно, не заполнив все поля. Но вы можете изменить worker поле к Option и заполните его позже:

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();
}

Призыв к engine.turn_on() заблокирует объект, чтобы убедиться, что он останется в области видимости. Тогда вам даже не нужны коробки для обеспечения безопасности, потому что объект станет неподвижным:

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();
}

Компилятор Rust не может использовать тот факт, что объект должен быть подвижным, потому что вещи, на которые он ссылается, хранятся в куче и живут как минимум столько же времени, сколько объект. Может быть, когда-нибудь в будущем. А пока вам приходится прибегать к небезопасному коду.

Другие вопросы по тегам