rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.83k stars 12.77k forks source link

Expand implicit conversion of functions to function pointers #58078

Open Thomasdezeeuw opened 5 years ago

Thomasdezeeuw commented 5 years ago

(I'm not sure if this requires a RFC, if so please let me know.)

Allow implicit conversion of functions to function pointers when trait is implemented on a function pointer. The code below shows the problem I'm having.

Calling accepts_fn works fine today, however calling accepts_my_fn doesn't work, even though the MyFn trait is implementation for function pointers. To make it work a explicit conversion (as fn(_) -> _) is required. This seems a bit surprising that in one instance a function in implicitly converted and another instance it is not.




fn add1(n: usize) -> usize {
    n + 1
}

fn accepts_fn(f: fn(usize) -> usize) -> usize {
    f(1)
}

trait MyFn {
    fn call(&mut self, n: usize) -> usize;
}

impl MyFn for fn(usize) -> usize {
    fn call(&mut self, n: usize) -> usize {
        (self)(n)
    }
}

fn accepts_my_fn<F: MyFn>(mut f: F) -> usize {
    f.call(2)
}

fn main() {
    let n = accepts_fn(add1);
    println!("n: {}", n);

    // This doesn't work.
    //let n = accepts_my_fn(add1);

    // We need explicit conversion.
    let n = accepts_my_fn(add1 as fn(_) -> _);
    println!("n: {}", n);
}

(playground)

Thomasdezeeuw commented 5 years ago

Can someone tell if this change would require a RFC?

Thomasdezeeuw commented 5 years ago

I still like some feedback on if this is wanted or not. If I could get some guidance I'm willing to write a pr for it.

CodeSandwich commented 5 years ago

There's one more point to add. Let's rename MyFn::call to MyFn::call_fn to avoid confusion with existing traits. Calling call_fn directly is impacted in the same way:

// doesn't work
add1.call_fn(5);
// works, but at what cost
(add1 as fn(_) -> _).call_fn(5);

I have the same problem, which I'm struggling with. Current coercion rules make APIs based on functions as first-class citizens barely usable.

PR would be probably very welcome. Unfortunately I can't say how hard will it be to actually implement if possible at all.

I think that core problem is that Rust makes only 1-step coercions for non-deref coercions. Fn items can coerce to fn pointers, fn pointers can coerce to trait implementors, but fn items can't make 2 steps and coerce to trait implementor directly. If that's really the case, https://github.com/rust-lang/rust/issues/18602 should solve it.

QuineDot commented 2 years ago

I think it's more that trait resolution doesn't fall back to consider implementations for types you could coerce to (no function items implement your trait.) It's not specific to function items and function pointers; compare here, where no arrays implement the trait.

For the function item / function pointer example, this change

-impl MyFn for fn(usize) -> usize {
+impl<F> MyFn for F where F: Fn(usize) -> usize {

Implements the trait for function items as well, allowing the un-casted version to compile.

Thomasdezeeuw commented 2 years ago

For the function item / function pointer example, this change

-impl MyFn for fn(usize) -> usize {
+impl<F> MyFn for F where F: Fn(usize) -> usize {

This doesn't work once the arguments or return types are also generic due to "unused generic" errors. Otherwise it would be my preferred implementation as well.