Isn't `Fn` just `FnOnce + Copy`?

pub fn call_twice<X, Y, F>(f: F, x1: X, x2: X) -> (Y, Y)
where
    F: FnOnce(X) -> Y + Copy,
{
    (f(x1), f(x2))
}

No, because there are non-Copy closures that satisfy Fn:

fn main() {
    use std::sync::Arc;
    let a = Arc::new(5);
    // dbg!(call_twice_copy(move |x| x*(*a), 2, 3)); // compile err
    dbg!(call_twice_fn(move |x| x*(*a), 2, 3));
}
5 Likes

Fn can capture Cells. A Copy + FnOnce type would need such capture to be in Rc for its closure to maintain state.

Also Copy is very strict so not all Fn* captures can be Copy.

2 Likes

This makes a much more compelling example than my original one, I think:

fn main() {
    use std::cell::Cell;
    let a = Cell::new(0);
    let accumulate = move |x| {
        let val = x + a.get();
        a.set(val);
        val
    };
    
    //dbg!(call_twice_copy(accumulate, 2, 3)); // compile err
    dbg!(call_twice_fn(accumulate, 2, 3));
}
1 Like

If I replace Copy with Clone, it will work again.

pub fn call_twice_clone<X, Y, F>(f: F, x1: X, x2: X) -> (Y, Y)
where
    F: FnOnce(X) -> Y + Clone,
{
    (f.clone()(x1), f(x2))
}

I wonder if Clone is what Fn does under the hook?

No, it does not clone.

To understand how it works, consider this example:

fn main() {
    let val = "foo".to_string();
    
    let my_fn = move || {
        println!("{}", val);
    };
    
    my_fn();
    my_fn();
}

That's equivalent to this:

#[derive(Clone)]
struct MyClosure {
    val: String,
}

impl MyClosure {
    fn call(&self) {
        println!("{}", self.val);
    }
}

fn main() {
    let val = "foo".to_string();
    
    let my_fn = MyClosure {
        val,
    };
    
    my_fn.call();
    my_fn.call();
}

On the other hand, this:

fn main() {
    let val = "foo".to_string();
    
    let my_fn = move || {
        println!("{}", val);
    };
    
    (my_fn.clone())();
    my_fn();
}

is equivalent to this:

#[derive(Clone)]
struct MyClosure {
    val: String,
}

impl MyClosure {
    fn call(&self) {
        println!("{}", self.val);
    }
}

fn main() {
    let val = "foo".to_string();
    
    let my_fn = MyClosure {
        val,
    };
    
    my_fn.clone().call();
    my_fn.call();
}

So one of them will clone the String, and the other one wont.

3 Likes

It will often compile, but it may produce different results due to the extra clone that @alice points out. For example, the accumulator I posted above:

fn main() {
    use std::cell::Cell;
    let a = Cell::new(0);
    let accumulate = move |x| {
        let val = x + a.get();
        a.set(val);
        val
    };
    
    assert_eq!((2,5), call_twice_fn(accumulate.clone(), 2, 3));
    assert_eq!((2,5), call_twice_clone(accumulate.clone(), 2, 3)); // Fails
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.