Unboxing boxed closures


#1

Continuing the discussion from Reusing mutable closure arguments:

Now I am passing closures by mutable reference, I have a function that is required to return a closure, which seems to work quite nicely with boxing:

    pub fn gt<A : PartialOrd>(a : &A, b : &A) -> bool {
        *a > *b
    }
    pub fn test2<R, A>(r : &mut R, a : &A, b : &A)
    where R : FnMut(&A, &A) -> bool {
        println!("{}", r(a, b));
    }
    pub fn complement_of_converse<'a, R, A>(r : &'a mut R) -> 
        Box<FnMut(&A, &A) -> bool + 'a>
    where R : FnMut(&A, &A) -> bool {
        Box::new(move |a, b| !r(b, a))
    }
    pub fn test3<R, A>(r : &mut R, a : &A, b : &A)
    where R : FnMut(&A, &A) -> bool {
        test2(&mut &mut *complement_of_converse(r), a, b);
    }
    test3(&mut gt, &3, &4);
    test3(&mut gt, &4, &4);

My question is, why when I unbox the closure do I need to pass a mutable reference to a mutable reference to the closure? By my thinking the object in the Box has type “R : FnMut(&A, &A) -> bool” and the type “test2” is expecting is “&mut R”, I would expect it to be “test2(&mut *complement_of_converse®, a, b);”. Obviously one of my assumptions is wrong, the question is, which one?

Another question is why doesn’t the boxed closure implement FnMut so I can pass it directly? My understanding would be the functions take an argument that implements a trait, rather than a trait object. If the compiler is able to pass the closure to ‘test2’ then it must be able to statically determine the type of the closure, and if it can do that why does the closure need to be boxed to return it? Is the compiler actually able to implement this whole program as a monomorphised static program?


#2

I can not explain to you what exactly is happening, but I think I can give a partial answer to your first question. When I am confused by the type system, I usually try to check my assumptions by assigning intermediate values to variables with manually specified types, like this:

let mut tmp: &mut FnMut(&A, &A) -> bool = &mut *complement_of_converse(r);
test2(&mut tmp, a, b);

This way, I can see where the type checker agrees with me. In this case, the above code does compile fine with that second &mut, but it does not without. I would assume that some kind of implicit dereferencing is occuring here, but I cannot explain it.

(A feature called type ascription is currently being implemented. When it is stable, the type could be ascribed directly to the expression, and the temporary variable would not be needed.)


#3

The problem is that FnMut is an unsized type, but type parameters are considered sized by default. In order to make your functions accept &mut R where R is FnMut, you need to have the bound R: ?Sized to remove the default Sized bound.

Edit: Due to issue #20403, you need to put the ?Sized bound on R in the type parameter list, not in a where clause.