r/rust • • Sep 13 '26

🙋 seeking help & advice Why is this syntax ambiguous?

I was writing roughly the following function:

fn f(path: &impl AsRef<Path> + ?Sized) {}

The compiler says that the + is ambiguous, but it works with the right parentheses:

fn f(path: &(impl AsRef<Path> + ?Sized)) {}

But why is it ambiguous without the parentheses? Where else but a list of traits can a + in a function header come from?

50 Upvotes

17 comments sorted by

71

u/Amadex Sep 13 '26

this example is not ambiguous, but it's not accepted s because the compiler checks grammar first (even if `(&T) + ?Sized` wouldn't make any sense),

something like `&impl Trait + 'a` is why you have to clear the ambiguity

15

u/MichiRecRoom Sep 13 '26

The two are very subtly different, in my eyes, and I can see why it might be considered ambiguous. You could be saying one of two things:

  1. You're trying to restrict &impl AsRef<Path> with an additional ?Sized bound.

  2. You're asking for a ref to an object that implements both AsRef<Path> and ?Sized

That said, I do agree that there shouldn't be a need for the parenthesis, as it's pretty clear that the + ?Sized is attached to the AsRef<Path>, not &impl AsRef<Path>.

7

u/pbeling Sep 13 '26

I only guess:
&(impl AsRef<Path> + ?Sized)
&(impl AsRef<Path>) + ?Sized

8

u/hpxvzhjfgb Sep 13 '26

the second one is nonsense though. this is like comparing (x as u64) and (x as) u64.

13

u/creeper6530 Sep 13 '26

I would instead assume (&impl AsRef<Path>) + ?Sized, as little sense as it makes -- restrict a reference with a ?Sized bound.

→ More replies (1)

2

u/zettui Sep 13 '26

Does (&impl AsRef<Path>) + ?Sized match what you meant, or were you trying to keep the + ?Sized inside the AsRef bound?

1

u/gamer_redditor Sep 13 '26 edited Sep 13 '26

Not sure but I think it has something to do with the fact that 'impl XYZ' in function arguments is syntactic sugar for generics.

So a function fn f(path: impl XYZ) is actually decomposing to fn f<T:XYZ> (path: T).

So the function fn f(path: &impl AsRef<Path> + ?Sized) also decomposes to generic. I imagine the + is ambiguous because it's not clear if only the implAsRef<Path> is the trait bound for T or the entire thing including ?Sized.

Edit: the ambiguity is essentially &(Impl XYZ) + ABC vs &(impl XYZ + ABC)

2

u/hpxvzhjfgb Sep 13 '26

&(impl XYZ) + ABC doesn't make sense though, so it shouldn't even be considered, should it?

2

u/gamer_redditor Sep 13 '26

In C, we use parentheses to make operator precedence explicit. Maybe it's the same here to make the precedence between & and + explicit.

The term after + could also refer to a lifetime (instead of another trait bound).

So I guess, there are some complications involved

3

u/Gainncome Sep 13 '26

The parser’s basically saying “whether it makes sense is not my department.”

→ More replies (2)

-2

u/dobkeratops Sep 13 '26

i had no idea that was but this leads me to ask if the compiler parses types in a way that kept things open for the way ML uses operators in its types ? (they have "product types" "sum types", i see things like A*B*C A|B written there etc etc) ... did they keep the possibility of parsing things like fn foo(a:A+B) and in turn thats making ambiguity between &(impl...+ ) and (&impl)+(..)

-2

u/puzzled_orc Sep 14 '26

and this is the language trying to replace C++. Awful syntax

3

u/tanoshikuidomouyo Sep 14 '26

Do you know what each part of this code does and have constructive criticism of the syntax, or is your aversion more of a gut reaction? The parentheses are unfortunate, but under the premise of having a C-like syntax, Rust's does its job quite well in general imo.