Value doesn't live long enough when put in str

2020-04-21 04:08发布

问题:

I'm trying to work with LLVM in Rust using this crate. I'm trying to create a code generator struct to hold the context, module, and builder for me, but when I try to compile I get an error message that says c does not live long enough. How can I get this to compile, and why isn't c living long enough?

Code:

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,
        }
    }
}

Full error message:

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

回答1:

This looks like one of those situations where lifetime elision makes things less clear.

Here's the prototype of Builder::new:

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

Which might make you think that the CSemiBox doesn't have any relation to the lifetime of context. But the definition of CSemiBox has a lifetime parameter:

pub struct CSemiBox<'a, D>

As I understand it, when the output type of a function (in this case Builder::new) has a lifetime parameter, it can be elided if there is only one input lifetime. (The lifetime elision rules are described in the book and in this question.) In this case, the output lifetime is taken to be the same as the input lifetime. That means the prototype from before is actually equivalent to the following:

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

I hope this clarifies what's happening: after Builder::new(&c), the CSemiBox contains a reference to the Context it was created from (b contains a reference to c). You can't put b and c in the same struct because the compiler has to be able to prove that c outlives b. For a more thorough explanation, see Why can't I store a value and a reference to that value in the same struct?

There are two ways I can think of to handle this. (You can't use Rc because you don't control the crate.)

  1. Don't store the Context inside the CodeGen struct. You're limited in how you can structure your code, but that's not necessarily bad.

  2. Since the Context is stored on the heap, you can use unsafe to make the references (appear to) have a 'static lifetime. Something like the following snippet ought to work, which removes the lifetime annotation from CodeGen. If you do this (as any time you use unsafe), you take responsibility for ensuring the safety of the exposed interface. That means, for example, CodeGen can't hand out references to builder and module, because that could leak a 'static reference to 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,
            }
        }
    }