6

I was able to make this code work:

fn twice<T: Clone>(fst: impl Fn(T), snd: impl Fn(T)) -> impl Fn(T) {
    move |t| {
        fst(t.clone());
        snd(t)
    }
}

However, what I want is this (without boxing):

fn sub<T: Clone>(mut fst: impl Fn(T), snd: impl Fn(T)) {
    fst = move |t: T| {
        fst(t.clone());
        snd(t)
    };
}

Is there a way I can make the second piece of code work without boxing, using traits, type casting or any other method? Rust complains that the types do not match.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Edgar
  • 159
  • 5
  • 2
    At first your approach is wrong, you are moving `fst` into `sub` function then trying to change it's value. but returning nothing ? `fst` is going to be dropped inside sub function. – Ömer Erden Jan 19 '19 at 18:46

2 Answers2

5

This cannot be done without boxing. The reason is that the actual type of fst in the input is different than the type of the closure that you later overwrite it with. The only way to make them the same type is with a trait object.

The boxed version might look like this:

use std::mem;

fn sub<'a, T: Clone + 'a>(fst: &mut Box<dyn Fn(T) + 'a>, snd: impl Fn(T) + 'a) {
    // Replace the original fst with a dummy closure while the new closure is being
    // constructed, to avoid the reference being temporarily invalid
    let fst_orig = mem::replace(fst, Box::new(|_| {}));
    *fst = Box::new(move |t: T| {
        fst_orig(t.clone());
        snd(t)
    });
}


fn main() {
    let mut f1: Box<dyn Fn(i32)> = Box::new(|x| println!("f1: {}", x));
    let f2 = |x| println!("f2: {}", x);

    sub(&mut f1, f2);

    f1(42);
}

But I really am not sure why you would want to do this!

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • And also this simple [code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d29a34ebe5a0cd6366da331463cae31) , check the error, it says: "note: no two closures, even if identical, have the same type", "help: consider boxing your closure and/or using it as a trait object" – Ömer Erden Jan 19 '19 at 20:57
  • *But I really am not sure why you would want to do this!* — This 100%. Just [reassign `fst` and let it get a new type](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=11c373e10f2b0f9a1b95867f2d0488f4). – Shepmaster Jan 20 '19 at 03:54
  • @Shepmaster I actually made an assumption that OP wanted to overwrite the input fn pointer, which wasn't quite expressed in the question. That's why I included an example `main` that uses the solution. If it was just a matter of reusing the variable name _inside_ the function, it seemed too trivial a request - but maybe that's what OP actually wanted? – Peter Hall Jan 20 '19 at 11:07
2

Answering the question you asked, no, you cannot yet assign a closure to a variable with the type impl Fn because you cannot yet declare such a variable:

fn foo() {
    let x: impl Fn() = move || println!("Hello");
}
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/lib.rs:2:12
  |
2 |     let x: impl Fn() = move || println!("Hello");
  |            ^^^^^^^^^

"But wait!" you say, "I have such a type in my function argument!". Truth is that no, you do not.

This syntax:

fn foo(x: impl Fn()) {}

Is just shorthand for this:

fn foo<F>(x: F)
where
    F: Fn(),
{}

You've simply constructed a generic and applied a trait bound to it.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366