Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot call implementations of the Fn* traits on references #42736

Open
shepmaster opened this issue Jun 18, 2017 · 13 comments
Open

Cannot call implementations of the Fn* traits on references #42736

shepmaster opened this issue Jun 18, 2017 · 13 comments
Labels
A-type-system Area: Type system C-bug Category: This is a bug. F-unboxed_closures `#![feature(unboxed_closures)]` P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@shepmaster
Copy link
Member

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.

@eddyb
Copy link
Member

eddyb commented Jun 18, 2017

cc @nikomatsakis Sounds like the overloaded call derefs too eagerly? Or is missing autoref?

@shepmaster shepmaster changed the title Can not implement the Fn* traits for references Cannot call implementations of the Fn* traits on references Jun 18, 2017
@Mark-Simulacrum Mark-Simulacrum added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-type-system Area: Type system labels Jun 23, 2017
@earthengine
Copy link

earthengine commented Jul 3, 2017

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 Fn Traits. It is about implementing traits for a trait object reference type.

@earthengine
Copy link

earthengine commented Jul 5, 2017

In the simplified example, I tried to defense the current compiler behavior.

The statement let t3: &T1 = t2; says

take t2 (which is a trait object of type &T2) and bind it to a variable t3, which is a trait object reference to something implements T1.

t2 is a reference, but pointing to T2, it is NOT a reference to T1. As t2 is 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; says

calculate expression &t2 and bind the result to a variable t1, which is a trait object reference to something implements T1.

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.

@nikomatsakis
Copy link
Contributor

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

@rust-highfive rust-highfive added the P-medium Medium priority label Jul 7, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-bug Category: This is a bug. label Jul 27, 2017
@alexreg
Copy link
Contributor

alexreg commented Aug 20, 2018

Progress on this lately? :-)

@alexreg
Copy link
Contributor

alexreg commented Jul 2, 2019

Are we in a good place to tackle this now that #45510 has been solved?

@Centril Centril added the F-unboxed_closures `#![feature(unboxed_closures)]` label Nov 5, 2019
@elycruz
Copy link

elycruz commented Jan 21, 2020

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

@eddyb
Copy link
Member

eddyb commented Jan 22, 2020

@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 an F: FnOnce() named type parameter).

(Btw, your link is just to the playground, not to your example - you need to press the Share button and copy the first link from there)

@elycruz
Copy link

elycruz commented Jan 22, 2020

@eddyb You're right (gotcha 👍 (updated my link 👍)).

@egasimus
Copy link

egasimus commented Dec 20, 2022

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.

@ijackson
Copy link
Contributor

ijackson commented Dec 17, 2023

@eddyb

.. this OTOH does error:

fn repro_ref(thing: &impl FnOnce()) {
    thing();
}

That code is simply wrong though. thing implements FnOnce so must be consumed, but it's only passed by reference.

@ijackson
Copy link
Contributor

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 could impl Fn for T?

@jacks0n9
Copy link

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.

@fmease fmease added A-type-system Area: Type system T-types Relevant to the types team, which will review and decide on the PR/issue. and removed A-type-system Area: Type system labels Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system C-bug Category: This is a bug. F-unboxed_closures `#![feature(unboxed_closures)]` P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests