Значение не живет достаточно долго, когда помещено в структуру

Я пытаюсь работать с LLVM в Rust, используя этот ящик. Я пытаюсь создать структуру генератора кода для хранения контекста, модуля и построителя для меня, но когда я пытаюсь скомпилировать, я получаю сообщение об ошибке с надписью c does not live long enough. Как я могу заставить это скомпилироваться и почему c живет недостаточно долго?

Код:

use llvm::*;
use llvm::Attribute::*;
pub struct CodeGen<'l> {
    context: CBox<Context>,
    builder: CSemiBox<'l, Builder>,
    module: CSemiBox<'l, Module>,
}
impl<'l> CodeGen<'l> {
    pub fn new() -> CodeGen<'l> {
        let c = Context::new();
        let b = Builder::new(&c);
        let m = Module::new("test", &c);
        CodeGen {
            context: c,
            builder: b,
            module: m,
        }
    }
}

Полное сообщение об ошибке:

error: `c` does not live long enough
  --> src/codegen.rs:17:31
   |
17 |         let b = Builder::new(&c);
   |                               ^ does not live long enough
...
24 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'l as defined on the body at 15:32...
  --> src/codegen.rs:15:33
   |
15 |     pub fn new() -> CodeGen<'l> {
   |                                 ^

error: `c` does not live long enough
  --> src/codegen.rs:18:38
   |
18 |         let m = Module::new("test", &c);
   |                                      ^ does not live long enough
...
24 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'l as defined on the body at 15:32...
  --> src/codegen.rs:15:33
   |
15 |     pub fn new() -> CodeGen<'l> {
   |                                 ^

error: aborting due to 2 previous errors

person BookOwl    schedule 01.03.2017    source источник
comment
Почему голос против?   -  person BookOwl    schedule 14.03.2017


Ответы (1)


Это похоже на одну из тех ситуаций, когда элизия на протяжении всей жизни делает вещи менее ясными.

Вот прототип Builder::new:

pub fn new(context: &Context) -> CSemiBox<Builder>

Это может заставить вас думать, что CSemiBox не имеет никакого отношения к времени жизни context. Но в определении CSemiBox есть параметр времени жизни:

pub struct CSemiBox<'a, D>

Насколько я понимаю, когда тип вывода функции (в данном случае Builder::new) имеет параметр времени жизни, его можно исключить, если существует только одно время жизни ввода. (Правила пожизненного исключения описаны в книге и в этот вопрос.) В этом случае время жизни вывода считается таким же, как время жизни ввода. Это означает, что предыдущий прототип фактически эквивалентен следующему:

pub fn new<'a>(context: &'a Context) -> CSemiBox<'a, Builder>

Надеюсь, это проясняет, что происходит: после Builder::new(&c) CSemiBox содержит ссылку на Context, из которого он был создан (b содержит ссылку на c). Вы не можете поместить b и c в одну структуру, потому что компилятор должен быть в состоянии доказать, что c переживает b. Для более подробного объяснения см. Почему я не могу сохранить значение и ссылку на это значение в одной структуре?

Я могу придумать два способа справиться с этим. (Вы не можете использовать Rc, потому что вы не контролируете ящик.)

  1. Не храните Context внутри структуры CodeGen. Вы ограничены в том, как вы можете структурировать свой код, но это не обязательно плохо.

  2. Поскольку Context хранится в куче, вы можете использовать unsafe, чтобы ссылки (кажущиеся) имели 'static время жизни. Должно сработать что-то вроде следующего фрагмента, который удаляет аннотацию времени жизни из CodeGen. Если вы сделаете это (как всякий раз, когда вы используете unsafe), вы берете на себя ответственность за обеспечение безопасности открытого интерфейса. Это означает, например, что CodeGen не может раздавать ссылки на builder и module, потому что это может привести к утечке 'static ссылки на context.

    pub struct CodeGen {
        context: CBox<Context>,
        builder: CSemiBox<'static, Builder>,
        module: CSemiBox<'static, Module>,
    }
    impl CodeGen {
        pub fn new() -> CodeGen {
            let c = Context::new();  // returns a CBox<Context>
            let c_static_ref: &'static _ = unsafe {
                let c_ptr = c.as_ptr() as *const _;  // get the underlying heap pointer
                &*c_ptr
            };
            let b = Builder::new(c_static_ref);
            let m = Module::new("test", c_static_ref);
            CodeGen {
                context: c,
                builder: b,
                module: m,
            }
        }
    }
    
person trentcl    schedule 01.03.2017
comment
@ FrancisGagné АГА! Это тот, на который я хотел ссылаться, но я смог найти только этот поиском. Спасибо - person trentcl; 02.03.2017
comment
Спасибо за отличный ответ! - person BookOwl; 02.03.2017
comment
Я думаю, что я просто не буду использовать структуру и заставлю вызывающий код создать контекст, модуль и построитель, а затем передать их функции генератора кода. - person BookOwl; 02.03.2017
comment
@Shepmaster На самом деле, я думаю, что ключом к этому ответу было осознание того, что 'a был опущен. Но я думаю, что я вдавался в подробности, и некоторые из них избыточны. - person trentcl; 10.03.2017