Oddity with move/Fn/lifetimes


#1

Can anyone explain why the code below requires a ‘move’ to compile? I cannot see why it is necessary (Note that none of the functions returns anything or side effects - there is nothing that can actually extend the lifetime of anything).

Substituting the type alias below into it’s single use, removes the need for ‘move’. Putting explicit lifetimes onto all the reference types, seems to make no difference.

pub struct State;

pub type Solver = for<'a> Fn(&'a State);

impl State {
    pub fn descend(&self, solver : &Solver) {
        solver(&State);
    }

    /* With this version of descend(), the code compiles without a 'move' on
     * 'chain' below.

    pub fn descend(&self, solver : &for<'a> Fn(&'a State)) {
        solver(&State);
    }

    */

    pub fn search(&self, depth : u8) {
        if depth < 80 {
            let depth = depth + 1;
            // The 'move' here is needed to get the code to compile.  But I
            // can't see why it is necessary.
            let chain = move |state : &State| state.search(depth);
            self.descend(&chain);
        }
    }
}

#2

Odd indeed. Without the move it complains about borrowing depth, which should get Copyd automatically…


#3

Playpen link for your code.

I think this should be reported as a bug. Can it be a difference in default lifetime bound for the trait object? Like the difference between &'a (Fn() + 'a) and &'a (Fn() + 'static).


#4

Can it be a difference in default lifetime bound for the trait object?

Seems likely

Since you’re borrowing depth (to copy it/deref it), the + 'static bound isn’t being met.

(the lifetime bounds on descend can be elided fwiw)


#5

Thanks for the explanations. So in summary, it seems:
A trait is not a type.
When you try to use ‘type’ to alias a trait, the trait is converted to a type by attaching a static lifetime.

Here is an example that does not use lambdas:

pub trait Trait { }
impl<'a> Trait for &'a u8 { }
type Alias = Trait;
type Lifed<'a> = Trait + 'a;

#[allow(unused_variables)]
pub fn ss(x : u8) {
    let s = &x;
    let t : &Trait = &s;                // This is fine
    let l : &Lifed = &s;                // This is fine
    let a : &Alias = &s;                // But this gives an error
}

It would make much more sense to me if the elided lifetime was generalised rather than using 'static. (Which would be consistent with other elided lifetimes).


#6

Nice example.

A trait has a type of the same name if the trait is object safe. It’s a dynamically sized type, so you use it behind a pointer.