user2525536
user2525536

Reputation: 396

Reference lifetime conflicts with same lifetime range

I am trying to store and use an optional callback handle in Rust which works like a method to the structure I am storing it in. It works as long as I do not pass a reference to itself to the callback. But doing so gives me a lifetime error for the used object references (E0312). The lifetime seems to be the same and I cannot figure out what to change to get this working.

type Callback<'a> = Fn(&'a mut Func, i32) -> i32;

struct Func<'a> {
    val: i32,
    func: Option<Box<Callback<'a>>>,
}

impl<'a, 'b> Func<'b> {
    fn exec(&'a mut self, val: i32) -> i32 {
        if let Some(ref f) = self.func {
            return f(self, val);
        };
        0i32
    }
}

fn main() {
    let mut a32 = Func{
        val: 10i32,
        func: Some(Box::new(|ref mut s, val: i32| -> i32 {
            let v = s.val;
            s.val += 1;
            val * 32 + v
        }))
    };
    println!("a32(4) = {}", a32.exec(4i32));
    println!("a32(4) = {}", a32.exec(4i32));
}

Is there a way to fix this or did I come across a compiler bug?

Using rustc 1.15.0 (10893a9a3 2017-01-19).

See also on Rust playground.

I also tried the same without explicit lifetimes but then I run into the problem that I cannot alias references in Rust (E0502).

I know that Rust tries to prevent this to avoid data races but would this mean that I always need to create a copy of my object in these cases?
The following does not work either giving me an error, that borrowed content cannot be moved out (E0507).

impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        if self.func.is_some() {
            return self.func.unwrap()(self, val);
        };
        0i32
    }
}

But I could not find a way to clone the boxed function...

Upvotes: 3

Views: 275

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 300099

You have a borrow issue here:

  1. You are borrowing self.func immutably
  2. You are attempting to borrow self mutably at the same time

This is not allowed, because it could allow you to change func while using it, which heralds troubles.


You could attempt to change Callback to only pass in &mut i32 instead, but then you would hit lifetime unification issues:

  • if you specify that exec takes &'a mut self, then you anchor the object, borrowing it for the rest of its lifetime,
  • on the other hand, if you specify a fresh lifetime, then by definition it's less than 'a, and you required 'a in the signature of Callback.

Neither situation works.


The solution, thus, is to avoid the lifetime in the first place.

It's also easier (on borrowing) NOT to pass an instance of self but just to pass a reference to self.val so I present that first:

type Callback = Fn(&mut i32, i32) -> i32;

struct Func {
    val: i32,
    func: Option<Box<Callback>>,
}

impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        if let Some(ref f) = self.func {
            return f(&mut self.val, val);
        };
        0i32
    }
}

fn main() {
    let mut a32 = Func{
        val: 10i32,
        func: Some(Box::new(|s: &mut i32, val: i32| -> i32 {
            let v = *s;
            *s += 1;
            val * 32 + v
        }))
    };
    println!("a32(4) = {}", a32.exec(4i32));
    println!("a32(4) = {}", a32.exec(4i32));
}

If you want to really pass Func, you need to "option dance":

impl Func {
    fn exec(&mut self, val: i32) -> i32 {
        let func = self.func.take();
        let res = if let Some(ref f) = func {
            f(self, val)
        } else {
            0i32
        };
        self.func = func;
        res
    }
}

And be aware that self.func is empty in the callback.

Upvotes: 3

Related Questions