Cannot call implementations of the Fn* traits on references
Attempting to implement FnOnce for a reference to a struct or a trait object:
#![feature(fn_traits)]
#![feature(unboxed_closures)]
struct S;
fn repro_ref(thing: &S) {
thing();
}
impl<'a> FnOnce<()> for &'a S {
type Output = ();
extern "rust-call" fn call_once(self, _arg: ()) -> () {}
}
fn main() {}
Produces the error:
error[E0618]: expected function, found `&S`
--> src/main.rs:7:5
|
7 | thing();
| ^^^^^^^
|
note: defined here
--> src/main.rs:6:14
|
6 | fn repro_ref(thing: &S) {
| ^^^^^
Interestingly, both of these alternatives work:
fn ok_ref_ref_arg(thing: &&S) {
thing();
}
fn ok_ref_ref(thing: &S) {
(&thing)();
}
cc #29625.
Originally from this Stack Overflow question.
cc @nikomatsakis Sounds like the overloaded call derefs too eagerly? Or is missing autoref?
Eduard-Mihai Burtescu at 2017-06-18 16:09:11
I am the one that posted the relevant StackOverflow question. I found some more example:
trait T1 { } trait T2 { } impl<'a> T1 for &'a T2 { } struct S { } impl T2 for S { } fn main() { let t2:&T2 = &S {}; let t1:&T1 = &t2; //This is OK let t3:&T1 = t2; //E0308: Expecting `T1`, found `T2` }Note the above does not require any features and behave the same in all channels. So, there has nothing to do with closures and
FnTraits. It is about implementing traits for a trait object reference type.earthengine at 2017-07-03 12:38:54
In the simplified example, I tried to defense the current compiler behavior.
The statement
let t3: &T1 = t2;saystake
t2(which is a trait object of type&T2) and bind it to a variablet3, which is a trait object reference to something implementsT1.t2is a reference, but pointing toT2, it is NOT a reference toT1. Ast2is a variable, not an expression, the compiler is not able to introduce code to convert the variable unless some automatic conversion applies here. Unfortunately, the language does not allow using implementation of traits to do automatic conversion.On the other hand,
let t1: &T1 = &t2;sayscalculate expression
&t2and bind the result to a variablet1, which is a trait object reference to something implementsT1.The difference here is we now have an expression, so it have to be calculated. The type checker will make sure the result is a reference to
T1, and to do this the compiler have to search for the trait implementation for&T2.So, although counter intuitive, but the current compiler behavior is not wrong. I think to achieve the designated usage, what we want to do is not implement a trait on top of a trait object, but to implement the conversion traits instead (already in my to-study list), so the compiler can apply the conversion automatically.
earthengine at 2017-07-05 02:17:03
I'm not sure what's going wrong. Have to trace through it I guess. I added this bug as a blocker to stabilizing. Going to call it P-medium.
triage: P-medium
Niko Matsakis at 2017-07-07 21:53:04
Progress on this lately? :-)
Alexander Regueiro at 2018-08-20 00:39:36
Are we in a good place to tackle this now that https://github.com/rust-lang/rust/issues/45510 has been solved?
Alexander Regueiro at 2019-07-02 03:20:20
One more example that works:
fn repro_ref(thing: impl FnOnce() -> ()) { thing(); }https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b53ffaf9ecf6bc8f4856d93b8d491993
Ely De La Cruz at 2020-01-21 17:24:40
@elycruz That's missing the reference though, this OTOH does error:
fn repro_ref(thing: &impl FnOnce()) { thing(); }Interestingly, the error is different with a generic like that (it doesn't have to be
impl FnOnce(), it could also be anF: FnOnce()named type parameter).(Btw, your link is just to the playground, not to your example - you need to press the
Sharebutton and copy the first link from there)Eduard-Mihai Burtescu at 2020-01-22 13:53:18
@eddyb You're right (gotcha 👍 (updated my link :+1:)).
Ely De La Cruz at 2020-01-22 15:55:25
Any news? If used wisely, generic callable "objects" can make for some very elegant APIs -- however here's a playground example of how this is still not possible.
egasimus at 2022-12-20 23:29:05
@eddyb
.. this OTOH does error:
fn repro_ref(thing: &impl FnOnce()) { thing(); }That code is simply wrong though.
thingimplementsFnOnceso must be consumed, but it's only passed by reference.Ian Jackson at 2023-12-17 15:45:40
Longer repro with current Nightly:
#![feature(fn_traits)] #![feature(unboxed_closures)] fn call_fn_owned(z: impl Fn()) { z(); } fn call_fn_ref(z: &impl Fn()) { z(); } struct RR; impl Fn<()> for &'_ RR { extern "rust-call" fn call(&self, (): ()) -> Self::Output { dbg!(); } } impl FnOnce<()> for &'_ RR { type Output = (); extern "rust-call" fn call_once(self, (): ()) -> Self::Output { dbg!(); } } impl FnMut<()> for &'_ RR { extern "rust-call" fn call_mut(&mut self, (): ()) -> Self::Output { dbg!(); } } fn main() { call_fn_owned(|| dbg!()); call_fn_ref(&|| dbg!()); call_fn_owned(&RR); call_fn_ref(&&RR); // RR(); <--- ERROR expected function, found struct `RR` // (&RR)(); <--- ERROR expected function, found struct `&RR` (&&RR)(); let r = RR; // r(); <--- ERROR expected function, found struct `RR` // (&r)(); <--- ERROR expected function, found struct `RR` (&&r)(); }https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=010b332c416581f94ff6d3a84c6c3e4d
I think the compiler isn't doing some autoref that it would for method despatch. Is this actually a blocking issue for #29625 (stabilising impl Fn) though? It can only bite you if you do something like
impl FnOnce for &T. Why would you do that when you couldimpl Fn for T?Ian Jackson at 2023-12-17 15:46:20
i think that if people really cared about this, it would have been fixed by now. can we just close this issue and make fn_traits stable.
Jackson at 2024-02-11 16:35:45