What is the exact rule for round brackets required around closure invocations?

In the "The Rust Programming language" book, at the closures chapter (printed page 272), there is a closure invocation that requires to be surrounded by round brackets:

// Simplified/edited version.
//
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn value(&mut self, arg: u32) -> u32 {
        (self.calculation)(arg)   // here!
    }
}

What is the exact rule? The book doesn't say more (at least, in this section).

In other contexts (of my attempts), they're not required:

// intermediate variable, on the same method
//
let calculation2 = &self.calculation;
calculation2(arg);

// general closure definition/invocation
//
let generic_closure = |x| x;
generic_closure(123);

Thanks!

You need to use parentheses if the closure is a field. Fields and methods can have the same name, so you use parens to differentiate the two.

struct Foo {
  bar: u32,
}

impl Foo {
  fn bar(&self) -> u32 {
    self.bar
  }
}

As an aside, this trick of having a private field then creating a public method with the same name is a common way of implementing a getter. Using self.bar() instead of self.get_bar() flows a bit nicer (in my opinion, anyway).

2 Likes

The example of (foo.bar)() requires parentheses because foo.bar() would be interpreted as a method call expression (i.e. it tries looking for a method called bar on foo). In general, parentheses are required if without them the expression would be interpreted differently due to precedence or ambiguities. For precedence see this table:

Operator/Expression Associativity
Paths
Method calls
Field expressions left to right
Function calls, array indexing
?
Unary - * ! & &mut
as left to right
* / % left to right
+ - left to right
<< >> left to right
& left to right
^ left to right
` `
== != < > <= >= Require parentheses
&& left to right
`
.. ..= Require parentheses
= += -= *= /= %=
&= ` = ^= <<= >>=`
return break closures

where higher up entries have higher precedence. See everything that’s below “function calls”.

Speaking in examples, this means things like a + b() are interpreted like a + (b()) and not (a + b)(), because + is further down than function calls. (Yes, with operator overloading a+b could be a closure or function pointer and thus callable.)

The case of the method call is actually more of a case of general ambiguity than just precedence (by the way, precedence is also just a tool to disambiguate syntax). Only considering the precedence, it would actually kind-of also make sense to interpret foo.bar() as (foo.bar)() since field expressions have higher precedence than function calls. However, this ambiguity is apparently disambiguated by a rule that the interpretation as a method call expression priority whenever it applies, and it makes sense: how else would you call methods if this wasn’t the case?

For some final considerations note that any postfix operators on the left hand side of a function call don’t need parentheses even if they are lower precendence. This means that for example foo.bar?() should be interpreted as ((foo.bar)?)(). (yep, it is.) The function call expression itself is also postfix, as is a method call, so chaining function calls like f()() or method-call and-then function call x.foo()() works, too (okay, the method call had higher precedence anyways).

And, since they are always callable, let’s also consider closure expressions. They would, in general, need to be paranthesized, as the precedence is higher, like for example || 42() is different from (|| 42)(), || {42}() is different from (|| {42})() (in both cases the former doesn’t compile), but looking in detail at closure expressions also reveals that in a closure expr. with return type, like |args| -> type { stmts; expr }, the braces become part of the closure expression and it ends afterwards. In effect this means that while (|| 42)() and (|| {42})() needed parantheses, by contrast || -> i32 { 42 }() or || -> _ { 42 }() do not.

4 Likes

Many thanks for the answers :star_struck:

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.