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.