Why can't I specify type parameters directly after the type?

Hi Rustaceans,

I wanted to type

let a = Vec<u64>::new();

but rustc complains "chained comparison operators require parentheses".

I see the recent 1.2.0 tells me help: use ::<...>instead of<...> if you meant to specify type arguments, so that's nice.

I can see this works:

Vec::<u64>::new()

Is there any simple explanation why it's not supported to have the type parameters directly after the type?

2 Likes

It's simply not intended by the language. The syntax and semantics of a language are very strict rules, which must be followed, so the compiler can tell sense out of your code.

The compiler was able to provide the ::<...> tip, because someone implemented a lint for it. Maybe because it is a general error done by newcomers.

1 Like

The reason is that Vec<u64> looks very much like vec<v64, i.e. a comparison between variables vec and v64 that evaluates to a boolean value. The next > token could still be a comparison, because you can compare two boolean values, and unless I am mistaken, ::new() could be a namespaced function call.

2 Likes

@Cigaes, so I suppose the answer to my question is that, unlike C++, Rust wants to have a context-free grammar? So the interpretation of < as a comparison or an angle-bracket around type parameters, cannot depend on whether the name before it is a type or a variable? Makes sense.

2 Likes

Really good decision of them in my opinion.

2 Likes

Yep, I can see the benefits of being context-free, and that this is a consequence, it's just a little surprising at first brush.

Although, it turns out Rust is only mostly context-free!

2 Likes

But is that really fully context free? I mean, it still uses < and lets it mean different things, it's just a matter of "is it coming right after :: or not"? But maybe you're right: it's context free in that sense, that right after :: it always means the same thing... It still can mean different things, but is more strict than C++ and C#. :slight_smile:

1 Like

The whole < ambiguity in C++ is what led to the ugly typename blah<T>::type and something.template func<T>() hacks.

4 Likes

Yeah, context free is a misnomer: it has a technical sense of "amendable to parsing by a push-down automaton", and a common sense "you don't need to see the context to decide what the thing is". It happens than technically context free languages are actually "context-sensitive" in the common sense.

So technically, even Rust's lexical grammar is not context free. (Proof: the culprit is raw string literals, you can intersect them with a regular language and apply the pumping lemma). But if we treat tokens as symbols, then the syntactic grammar looks like context free (there are a couple of LALR style grammars out there, but as far as I know neither have been extensively compared with the canonical rustc parser).

In practice, Rust is relatively easy to parse by an LL-style parser, though there is some weirdness around semi-statement blockish expressions like ifs, while , match, etc, and there are surprising rules about struct literals in conditionals.

4 Likes

context free grammar
c++ is so difficult to parse because of and expression ambiguities (consider what can happen e.g. with expressions in type-params aswell..), it needs to resolve all symbols first.. this is a nightmare and constrains tooling

as a newcomer this behavior was confusing for me. is there a rust documentation explaining where :: is applicable and where it's unnecessary?

let _ = Vec<String>::new();   // error: use `::<...>` instead of `<...>` if you meant to specify type arguments
let _ = Vec::<String>::new(); // fix (note: compiler expects `::`)
let _: Vec::<String> = Vec::new(); // unnecessary path disambiguator; try removing `::`
let _: Vec<String> = Vec::new();   // fix (note: compiler does NOT expect `::`)
let _: Vec<String> = Vec::<String>::new(); // lhs and rhs have different expectations on `::`
2 Likes

It's needed in values and not needed in types.

4 Likes

"context-free" has a very specific technical, mathematical meaning in the theory of formal languages. Sure, in a human sense, the meaning of < depends on other "context", but technically, the grammar of Rust is (almost, I think) context-free in the sense that it's possible to unambiguously parse Rust code without having to maintain a symbol table or otherwise intermingle parsing and semantic analysis/type checking in the compiler. (Incidentally, it's exactly this property of context-free grammars that makes them so much nicer to work with when writing a compiler.)

1 Like

Keyword: turbofish

2 Likes

To be concrete, this example (playground) shows the illegal syntax together with the two variations that do work:

#![allow(non_upper_case_globals)]

const Vec: u32 = 10;
const u32: u32 = 20;
fn new() -> bool { true }

fn main() {
    let a = Vec::<u32>::new();
    let b = (Vec < u32) > ::new();
    // let c = Vec<u32>::new(); // does not compile
    println!("a: {:?}", a);
    println!("b: {:?}", b);
}

So a is a vector, and b is a Boolean. The third option doesn't compile, it requires you to pick one of the other syntaxes to be explicit about what you mean.

9 Likes