Questions about E0599 error


struct Foo {
    bar:fn(&Foo)
}

impl Foo {
    fn bar1(&self) {
        println!("Hello from bar1");
    }
    fn bar2(&self) {
        println!("Hello from bar2");
    }
}

fn main () {
    let foo1 = Foo {
        bar:Foo::bar1
    };
    let foo2 = Foo {
        bar:Foo::bar2
    };
    foo1.bar(&foo1);
    foo2.bar(&foo2);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0599]: no method named `bar` found for struct `Foo` in the current scope
  --> src/main.rs:22:10
   |
2  | struct Foo {
   | ---------- method `bar` not found for this struct
...
22 |     foo1.bar(&foo1);
   |          ^^^ field, not a method
   |
help: to call the function stored in `bar`, surround the field access with parentheses
   |
22 |     (foo1.bar)(&foo1);
   |     +        +
help: there is a method with a similar name
   |
22 |     foo1.bar1(&foo1);
   |          ~~~~

error[E0599]: no method named `bar` found for struct `Foo` in the current scope
  --> src/main.rs:23:10
   |
2  | struct Foo {
   | ---------- method `bar` not found for this struct
...
23 |     foo2.bar(&foo2);
   |          ^^^ field, not a method
   |
help: to call the function stored in `bar`, surround the field access with parentheses
   |
23 |     (foo2.bar)(&foo2);
   |     +        +
help: there is a method with a similar name
   |
23 |     foo2.bar1(&foo2);
   |          ~~~~

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` due to 2 previous errors

This code paraphrases some actual code where I have a struct that gets instantiated for two slightly different cases. In particular, there are a pair of similar but slightly different functions that need to be called in each of the cases, with the struct instance as an argument.

The advice given in the error message does work -- adding the parens results in error-free compilation and the code runs correctly. My question, though, is where is this documented? I've searched, but cannot find the justification or explanation for why this works. It's certainly possible it's there and I missed it.

I'd like to understand the language principle that applies here. It would also be great if the error message from the compiler, while helpful as it is, gave a hint as to why this works.

One addition: this code also produces the two E0599s:

struct Foo {
    bar:fn(&Foo)
}

fn bar1(foo:&Foo) {
    println!("Hello from bar1");
}
fn bar2(foo:&Foo) {
    println!("Hello from bar2");
}


fn main () {
    let foo1 = Foo {
        bar:bar1
    };
    let foo2 = Foo {
        bar:bar2
    };
    foo1.bar(&foo1);
    foo2.bar(&foo2);
}

From the compiler:
Compiling playground v0.0.1 (/playground)
error[E0599]: no method named `bar` found for struct `Foo` in the current scope
  --> src/main.rs:22:10
   |
2  | struct Foo {
   | ---------- method `bar` not found for this struct
...
22 |     foo1.bar(&foo1);
   |          ^^^ field, not a method
   |
help: to call the function stored in `bar`, surround the field access with parentheses
   |
22 |     (foo1.bar)(&foo1);
   |     +        +

error[E0599]: no method named `bar` found for struct `Foo` in the current scope
  --> src/main.rs:23:10
   |
2  | struct Foo {
   | ---------- method `bar` not found for this struct
...
23 |     foo2.bar(&foo2);
   |          ^^^ field, not a method
   |
help: to call the function stored in `bar`, surround the field access with parentheses
   |
23 |     (foo2.bar)(&foo2);
   |     +        +

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` due to 2 previous errors

This is very odd, in my opinion, since no methods have been declared with impl and the word 'self' does not appear. It seems to think lines 22 and 23 are attempted method calls because they are in x.y form where x is a struct instance. And then, of course, the question of why (x.y) turns a field reference into a method call here is even more of a mystery than the first case, at least to me.

Thanks in advance for any help provided.

Rust allows you to define a method with the same name as a field if you want, which means that the parser has to have some way of deciding whether x.y refers to a field or a method. To keep things simple and unambiguous, this is not decided based on whether a field or method actually exists with that name, but purely based on the context where it's used: if you write x.y(...) this is always parsed as a method call, and if you write any other expression involving x.y that doesn't follow it with parens then it's always parsed as a field access.

(x.y)(...) works because the parens cause x.y to be parsed as a separate expression first, just like any other use of parens for precedence. x.y is a field access, and you have a field y that contains a function pointer, so the expression (x.y) evaluates to that function pointer, which is then called using the usual syntax for calling functions.

3 Likes

Excellent explanation. Thank you. Can you point me to somewhere in the docs where this is explained?

I'd add that I'm surprised that foo1.bar(&foo1) is thought to be a method call based simply on its syntax, ignoring the fact that there is no bar method, as you explain. The error message even says that there is no bar method, so the compiler knows this. It also knows the bar field is a function; it says so. Admitting that I have zero understanding of rustc internals, I'll still venture the opinion that this could have been better handled by using what the compiler says it knows to treat foo1.bar as a field reference, the only thing it could possibly be, rather than asking me to put parens around it to force it to be a field reference. I think this is worth filing an issue, which I will do.

Thanks again for the crystal-clear explanation.

It knows it didn't find a method, at which point your program couldn't compile. It might not have gone looking for a callable field until after the fact (so it could give a more useful error).

To elaborate on what I mean, note that method call resolution looks in potentially a lot of places for the method. Not just inherent methods, but any visible traits; not just for the type of the expression, but for an auto-ref and all auto-derefs of the expression.

foo1 probably does have a bar method if you import the right random trait from crates.io :slight_smile:

If things worked the way you described, implementing a trait for function pointers could either break far away code or silently change its behavior. (Which may seem like an odd thing to do, but it's easy to do with blanket implementations.)

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.