How to store a struct that requires a reference with lifetime

Dear rust community,

I am using the following package https://docs.rs/z3/latest/z3/ to interact with an SMT solver. However the package has some lifetime uses which I am not able to create a wrapper for.

My goal is to be able to store a Solver in a struct and have a Wrapper::new() method without needing to specify the context of the solver.

The solver takes a reference to a context, so the context must live as long as the wrapper. If only the solver would take ownership of the context, the issue would be solved, but unfortunately i cannot change the library.

I tried the following: (both with and without comments) but both don't work because of referencing a variable in local scope.

use z3::{Solver, Config, Context};

#[derive(Debug)]
struct Wrapper<'ctx> {
    // ctx: &'ctx Context,
    solver: Solver<'ctx>,
}

impl<'ctx> Wrapper<'ctx> {
    fn new() -> Self {
        let cfg = Config::new();
        let ctx = Context::new(&cfg);
        let solver = Solver::new(&ctx);
        Self { 
            // ctx: &ctx,
            solver 
        }
    }
}

fn main() {
    let solver = Wrapper::new();
    dbg!(solver);
}

I am pretty new to lifetimes so I don't really know how to solve this issue. Do you have any ideas/directions to be able to store such a solver?
Thanks in advance!

What you're trying to do (effectively) is called a self-referential struct, ie, a struct which has one field which stores a reference to another field. This is not allowed in Rust.
To be more specific:

  • You are getting the "reference-to-temporary" error since the variable ctx is freed once new returns, but the Wrapper you are returning still refers to it. Hence that would be a dangling reference and is hence not allowed.
  • If you try to store ctx by value instead in Wrapper, you'd end up with the self-referential problem.

One slightly bad way to solve your problem is to use Box::leak. Effectively, you are allocating memory for the Context and forgetting about it, allowing it to leak. This solves the reference error, but if you allocate a lot of Wrappers, then you would end up leaking all the memory.

#[derive(Debug)]
struct Wrapper<'ctx> {
    solver: Solver<'ctx>,
}

impl<'ctx> Wrapper<'ctx> {
    fn new() -> Self {
        let cfg: &'static Config = Box::leak(Box::new(Config::new()));
        let ctx: &'static Context = Box::leak(Box::new(Context::new(&cfg)));
        let solver = Solver::new(ctx);
        Self { solver  }
    }
}
1 Like

Thanks for your quick reply and a possible solution! In my use case I would like to be able to create a large amount of solvers :see_no_evil: and be able to cleanup ones that are no longer in use. Is there any other way this can be solved? Or is this a the consequence of a design decision in the z3 crate?

The consequence is that you cannot store the Context and the Solver in the same struct. You can store them in separate structs, or separately at top level.

Make the Config struct outside and pass it as an argument to Wrapper::new. You don't need to leak any memory.

Rough example:

let config = Config::new();
loop {
    let wrapper = Wrapper::new(&config);
}
2 Likes

I understand that that works for one config, but lets say i have a struct with a vector of these wrappers. I am back to square one right?

For example take the following code:

use z3::{Solver, Config, Context};

#[derive(Debug)]
struct Wrapper<'ctx> {
    solver: Solver<'ctx>,
}

impl<'ctx> Wrapper<'ctx> {
    fn new(ctx: &'ctx Context) -> Self {
        let solver = Solver::new(ctx);
        Self { 
            solver 
        }
    }
}

fn main() {
    let mut wrappers: Vec<Wrapper> = vec![];
    for _ in 0..3 {
        let config = Config::new();
        let ctx = Context::new(&config);
        let wrapper = Wrapper::new(&ctx);
        wrappers.push(wrapper);
    }
    dbg!(wrappers);
}

How about:

fn main() {
    let configs: Vec= (0..3).map(|_| Config::new()).collect();
    let contexts: Vec<_> = configs.iter().map(|cfg| Context::new(cfg)).collect();
    let wrappers: Vec<_> = wrappers.iter().map(|ctx| Wrapper::new(ctx)).collect();
    dbg!(wrappers);
}

Essentially, you first store the Config's and the Context's and create the Wrapper's based off them.

The important thing is that Config must stay alive for the whole lifetime of your wrapper. So in that case it should outlive your Vec.

When you create it in the same loop as the wrapper, it gets dropped at the end of the current iteration, so you can't keep it in a longer lived Vec.