Option's or_else

I'm trying to wrap a queue which internally contains two queues, so it behaves like normal_queue is appended to favored_queue.

pub struct FzDQueue {
    favored_queue: VecDeque<QEntry>,
    normal_queue: VecDeque<QEntry>,
}

And for get_mut(i), I would like to get reference of the entry firstly from favored_queue and if failed from normal_queue.
Option::or_else seems an exact match

pub fn get_mut(&mut self, pos: usize) -> Option<&mut QEntry> {
    let favored_len = self.favored_queue.len();
//        let favored_entry = self.favored_queue.get_mut(pos);
//        if favored_entry.is_some() {
//            return favored_entry;
//        } else {
//            return self.normal_queue.get_mut(pos - favored_len);
//        }
    return self.favored_queue.get_mut(pos).or_else(|| self.normal_queue.get_mut(pos - favored_len));
}

However it emits an error:

error[E0373]: closure may outlive the current function, but it borrows self, which is owned by the current function
--> src/lib/queue/fzd_queue.rs:49:56
|
49 | return self.favored_queue.get_mut(pos).or_else(|| self.normal_queue.get_mut(pos - favored_len));
| ^^ ---- self is borrowed here
| |
| may outlive borrowed value self
|
help: to force the closure to take ownership of self (and any other referenced variables), use the move keyword
|
49 | return self.favored_queue.get_mut(pos).or_else(move || self.normal_queue.get_mut(pos - favored_len));

I guess it's something due to FnOnce or_else takes (Option in std::option - Rust), which should be "static".

OTOH, the code below works fine.

fn pick_next_favored(&mut self) -> Option<QEntry> {
    return self.favored_queue.pop_front();
}

fn pick_next_normal(&mut self) -> Option<QEntry> {
    return self.normal_queue.pop_front();
}

pub fn pick_next(&mut self) -> Option<QEntry> {
    return self.pick_next_favored().or_else(|| self.pick_next_normal());
}

So my questions are:

  1. Why the second code compiles fine why the first fails?
  2. Is there any way to use or_else in the first case?
1 Like

try this:

    pub fn get_mut(&mut self, pos: usize) -> Option<&mut QEntry> {
        let favored_len = self.favored_queue.len();
        let fq = &mut self.favored_queue;
        let nq = &mut self.normal_queue;
        fq.get_mut(pos).or_else(
            move || nq.get_mut(pos - favored_len),
        )
    }

Alternatively, you have the length of the first list. You could just do this:

pub fn get_mut(&mut self, pos: usize) -> Option<&mut QEntry> {
    let favored_len = self.favored_queue.len();
    if pos < favored_len {
        self.favored_queue.get_mut(pos)
    } else {
        self.normal_queue.get_mut(pos - favored_len)
    }
}

That's very much like the one I commented in the first code snippet :joy:

That works!

No I'm very curious about how rustc determines the these two snippets :joy:

self is borrowed here:

self.favored_queue.get_mut(pos)

so rustc can not move self into the closure, instead it borrow self, which means the type of self in clousre is &'a &'b mut self. life time 'a is less then 'b', then the error occurs.

1 Like

The problem is that when you return Option<&mut QEntry> the mutable borrow of self is extended out to the caller - it doesn't end after the get_mut call. I'm actually surprised it doesn't instead complain about trying to borrow mutably more than once at a time. I suspect it just fails while trying to synthesize the closure type and doesn't get far enough. The closure likely contains a &'a mut &'b mut self; if you try to create your own struct like:

struct __Closure<'a,'b> {
    _self: &'a mut &'b mut FzDQueue
}

You'll see the compiler complains about the lifetimes - it cannot guarantee that 'b:'a. If you make the struct signature struct __Closure<'a, 'b: 'a> then everything's fine, but I suspect the synthesized closure does not do that.

Once you split the borrows to favored_queue and normal_queue, as @nooberfsh showed, the compiler doesn't complain because as far as it's concerned you're not trying to borrow the same value mutably more than once at a time - they're disjoint borrows.

This is one of those cases where the original code should just work, but compiler isn't "smart" enough/doesn't see through the code.

1 Like