Borrow in a closure

#1

Hi, I never gave this a thought but now I’m struggling to explain to myself how the borrowing happens in rust closures. Consider this playground link:

fn main() {
    #[derive(Eq, PartialEq, Clone)]
    struct A { a: Vec<u8> }
    
    let a = A { a: vec![1, 2, 3] };
    let c = A { a: vec![1, 2, 3] };
    
    let mut b = vec![a.clone(), a.clone(), a];
    
    // if let Some(pos) = b.iter().position(|elt| elt == c) { // This will error out
    if let Some(pos) = b.iter().position(|elt| *elt == c) {
        let _ = b.remove(pos);
        b.push(c);
    }
}

I get that the closure is borrowing c. The borrow can be either & or &mut. In this case it’s & (i think). So why do i have to do *elt == ? Why doesn’t elt == c work ? as elt and borrowed c inside closure should both have the type &A ? If A was copy-able i could understand that maybe a copy of it was created and then the value semantics makes sense. However it’s not (it’s move-able). So if c inside the closure has type A then that would imply it was moved. How can i then use it in b.push(c) ? Of-course i think it wasn’t moved (and the closure doesn’t even have move prefix) and hence my confusion.

#2

Semantically, the c inside the closure has exactly the same type as the c outside. The actual capture behind the scenes will depend on how the variable is used.

I think the additional trick you’re missing is that == calls PartialEq::eq with references to its two operands. You have elt: &A since you’re iterating by reference, and c: A because that’s how that variable was defined, regardless of the closure.

So *elt == c matches the types, calling <A as PartialEq>::eq(&*elt, &c). Since the closure is only using c by reference, it doesn’t need to capture/move the actual value.

(and the closure doesn’t even have move prefix)

It’s not necessary to use the move prefix if a closure does something that actually requires a move. But sometimes you want to use move anyway so the closure is not restricted to the borrowed scope of its captures. For instance, if you wanted to return an iterator like iter.filter(|elt| *elt == c), then you would need the closure to own c to be returned with it, rather than just borrowing a reference.

4 Likes
#3

It seems that you don’t know how closures are desugarred, the desugarring is really simple and knowing it will help you understand why what @cuviper said is true. You can read about it in my blog on the topic here:

2 Likes
#4

No i think i had guessed about captures very similar to what your blog says:

let mut counter: u32 = 0;
let delta: u32 = 2;
...
let mut next = __closure_4__ {
    counter: &mut counter,
    delta: &delta
};

So for me in my example, the c outside the closure has the type A. The c inside the closure has the type &A (which i also said in my OP) and it references the c outside the closure. This is also what you blog says - so pretty sure we are in the same page there.

The thing that I was perhaps missing was what @cuviper said about PartialEq taking the LHS RHS by reference. Now it all makes sense. *p == c becomes PartialEq::eq(&self, &A). Since c inside the closure is of type &A already, there’s no problem and things work. You need to deref p just like normally one would do obj.mem_fun() (where obj: Object) which would translate to mem_fun(&self) <==> mem_fun(&Object).

With that in place things make sense to me now.

#5

Here is your code slightly rewritten to explicit the stuff mentioned in this thread:

#[derive(Eq, PartialEq, Clone)]
struct A {
    a: Vec<u8>,
}

fn main ()
{
    let mut b = {
        let a = A {
            a: vec![1, 2, 3],
        };
        vec![a.clone(), a.clone(), a]
    };
    let c = A { a: vec![1, 2, 3] };

    let closure = |elt: &A| -> bool {
        // here, the value actually captured is &c :
    //  A::eq(elt, &c)
    //  *elt == *(&c)
        *elt == c
    };
    fn assert_Fn (_: &impl Fn(&A) -> bool) {}
    assert_Fn(&closure);

    if let Some(pos) = b.iter().position(closure) {
        let _ = b.remove(pos);
        b.push(c);
    }
}

The important thing to understand is that c within the closure is sugar for *(&c), where &c is the actual value captured by the closure (thanks to _ == _ only taking references to its operands):


struct Closure<'c> {
    at_c: &'c A,
}

impl<'elt> Fn<(&'elt A,)> for Closure<'_> {
    extern "rust-call"
    fn call (
        self: &'_ Self,
        (elt,): (&'elt A,),
    ) -> bool
    {
        *elt == *self.at_c
    }
}

fn main ()
{
    let mut b = {
        let a = A {
            a: vec![1, 2, 3],
        };
        vec![a.clone(), a.clone(), a]
    };
    let c = A { a: vec![1, 2, 3] };

    let closure = Closure { at_c: &c };
    fn assert_Fn (_: &impl Fn(&A) -> bool) {}
    assert_Fn(&closure);

    if let Some(pos) = b.iter().position(closure) {
        let _ = b.remove(pos);
        b.push(c);
    }
}
1 Like