Calling function in struct field requires extra parenthesis


#1

I ran into a curious error from the compiler today. I wanted to write some code that operates on a matrix and figured I could let the user specify the matrix via an accessor function of type Fn(usize, usize) -> usize. Code like this:

struct Foo<M: Fn(usize, usize) -> usize> {
    matrix: M,
}

fn main() {
    let foo = Foo { matrix: |i, j| i + j };
    println!("foo(3, 4): {}", (foo.matrix)(3, 4));
}

The part that I found curious is that I must wrap foo.matrix in parenthesis in order to call the function. Without it, the compiler tells me:

error[E0599]: no method named `matrix` found for type `Foo<[closure@src/main.rs:6:29: 6:41]>` in the current scope
 --> src/main.rs:7:35
  |
7 |     println!("foo(3, 4): {}", foo.matrix(3, 4));
  |                                   ^^^^^^ field, not a method
  |
  = help: use `(foo.matrix)(...)` if you meant to call the function stored in the `matrix` field

That’s a really helpful error message, but it still feels like a flaw to me that extra parenthesis can change the meaning of a program. I’m used to thinking of parenthesis as a tool for forcing precedence, and as such, (x) and x normally mean the same when x is not a compound expression.

Thoughts?


#2

It just occurred to me that one can even “hide” the error by defining a method with the same name as the field:

impl<M: Fn(usize, usize) -> usize> Foo<M> {
    fn matrix(&self, i: usize, j: usize) -> usize {
        (self.matrix)(i, j)
    }
}

That is, fields and methods are in separate namespaces and the namespaces can overlap. When you write foo.matrix and use it as a value, then you’re accessing the matrix field. When you write foo.matrix(), you’re accessing the matrix method.

I suppose that’s pretty consistent :slight_smile:


#3

Right. I’ve found its pretty useful to have an accessor method that’s the same name as the field its wrapping.

More generally, since fields are mostly internal implementation details whereas methods are API, its nice that the implementation details don’t affect the names you can use in your API. Otherwise you could end up doing an annoying field rename just to give a method its most natural name (and in practice you might put that off to later, leaving a method with an awkward name because of an implementation detail).


#4

Yeah, I can see how it’s convenient to have the two namespaces be separate.

I’ve mostly programmed in Python, and there methods and fields share a common namespace. So foo.x means "give me the field or method named x". If it’s a method, then you can later call the value.

This doesn’t seem to work in Rust, that is, I cannot do

let method = foo.matrix;
method(10, 20);

where the foo struct has an x method. I must instead create a closure:

let method = |i, j| foo.matrix(i, j);
method(10, 20);

I had hoped the compiler would do this for me, but I can see how it doesn’t play well with the idea of a strict separation between fields and methods.


#5

You can borrow the closure (assuming it’s a field) and call through it:

let method = &foo.matrix;
method(10, 20);

#6

Yes, thanks. That was actually clear to me. The issue is what happens if foo.matrix is a method – then the above code doesn’t compile.

This has been a fun little exploration of Rust’s syntax. For consistency and uniformity (and since I come with a Python background), I had imagined that

(foo.matrix);

and

foo.matrix

would always mean the same.

Similarly, since Rust has values that can be called (callables in Python), it would have made sense in my mental model if

foo.matrix

would mean

|i, j| foo.matrix(i, j)

when foo.matrix is a method and there is no matrix field. If there was this equality, I could interpret a line like

foo.matrix(10, 20)

as meaning

  1. find the callable foo.matrix
  2. call it with arguments 10 and 20

Now, it turns out that Rust does not work in the same uniform way as Python does (most languages are less uniform than Python, except perhaps Lisp?) and so the expression

foo.matrix(10, 20)

cannot be broken down as I imagined.

Note, however, that normal function calls can be broken down in this way:

fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    let func: fn(i32) -> i32 = square;
    println!("square(3): {}", func(3));
}

This probably just serves to show that functions and methods are different. Creating the kind of closure I talk about for a method value would perhaps incur some allocation and that might be why it has to be done explicitly?

I hope the exploration will be helpful for others who come with a similar background as me.


#7

You can achieve close to what you want, I think. Consider:

struct Foo<M: Fn(usize, usize) -> usize> {
    matrix: M,
}

impl<M: Fn(usize, usize) -> usize> Foo<M> {
    fn matrix(&self, x: usize, y: usize) -> usize {
        (self.matrix)(x, y)
    }
}

struct BoxFoo {
    matrix: Box<Fn(usize, usize) -> usize>,
}

impl BoxFoo {
    fn matrix(&self, x: usize, y: usize) -> usize {
        (self.matrix)(x, y)
    }
}

fn main() {
    let foo1 = Foo {
        matrix: |i, j| i + j,
    };
    // This func can only be invoked on the precise `Foo` above cause it's generic and each instantiation with a different closure produces a different type
    let func = Foo::matrix;
    println!("{}", func(&foo1, 3, 4));

   // This is a boxed  (i.e. type erased) version - you can call this on any BoxFoo
    let func = BoxFoo::matrix;
    let boxedfoo = BoxFoo {
        matrix: Box::new(|i, j| i * j),
    };
    println!("{}", func(&boxedfoo, 3, 4));

    let boxedfoo2 = BoxFoo {
        matrix: Box::new(|i, j| i * j * 2),
    };
    println!("{}", func(&boxedfoo2, 3, 4));
}

#8

I totally understand your expectation, coming from Python, and I’ve wished for that simple syntax a few times.

I think the reason is that in Rust’s case, what looks to be a simple member access would actually be more involved, creating a closure. Don’t forget that creating the closure also has to borrow or move ownership of the receiver. (This is in contrast to free functions, where the function pointer is really just that, with no ownership implications.)


#9

If it worked without the parenthesis then it would be changing the meaning.

impl<M: Fn(usize, usize) -> usize> Foo<M> {
    fn bar() -> usize { 42 }
}
println!("foo.bar: {}", foo.bar());

Gives same error. (This case you can’t add the parenthesis.)

struct Foo<M: Fn(&Foo<M>, usize, usize) -> usize> {
    matrix: M,
}
fn main() {
    let foo = Foo { matrix: |_, i, j| i + j };
    println!("foo(3, 4): {}", foo.matrix(3, 4));
}

This still fails. &Self isn’t allowed. The compiler is strict that not having self becomes an associated function. e.g. in Rc