I've used other languages with generic types. I always thought of it as similar to a function call, passing arguments to an abstraction. So:
some_function(a, b)
Creates an "instance" (a call) of some_function by passing specific arguments a and b.
SomeType(a, b)
Creates an instance of the class/type SomeType, calling its constructor with arguments. Not all languages have this exact syntax, but some do (Kotlin, Swift, Python, C++). And lastly:
HashTable<Int, String>
Creates an instance of a type, by passing two arguments to a generic type. All three share this idea of applying/calling an abstraction with arguments. In the last, the () changes to <>, and the application there is a compile-time thing, not a runtime call.
I understand the problems with parsing that last one, because < and > are reused for comparison operators. Rust's syntax appears to be:
HashTable::<i32, String>
This confuses me because :: usually means to me that we're drilling down in a path of some kind. Should I still think of that as "the type HashTable<i32, String>"? So ::< ... > is simply how you "apply/call/instantiate" the generic type and pass it arguments. Or should I think of :: as some kind of path drilling?
I would argue that HashTable<Int, String> is the "real" Rust syntax. Doing a quick grep through some Rust repositories I have open, Foo<Bar> syntax appears about 5 times more often than Foo::<Bar> syntax.
I think of the turbofish syntax as merely a variant of the normal Foo<Bar> syntax. This variant exists purely to avoid the parsing ambiguity that you mentioned. It is required only in paths in expressions.
You could think of the :: in this syntax as a signal to the parser (and the reader) that the preceding tokens are part of a path/type rather than an expression on their own. Or just think of ::< as an alternate, disambiguated form of the < bracket.
HashTable::<i32, String>
This syntax just exists due to parsing simplicity reasons iirc, its not due to any other internally-consistent logic. You shouldn't think of it as anything beyond "I need to put :: in this one weird case". You should think of it as the same (logically) as HashTable<K,V>.
Reminder that if you prefer to not write the turbofish-::, you can just put the parser into type mode directly instead: <HashMap<i32, String>>::new()works great.
The problem is that in expression mode x < b is a comparison, so the ::< is needed to distinguish.
I think that mental model struggles when handling the combinatorial complexity of having multiple generic arguments, especially when you start getting into trait bounds and nested generics (even if it is essentially how monomorphization works). Though it might be interesting to see a language that did take that approach...
Rust developers haven't liked to have a language where you couldn't even answer a simple question “is this syntactically valid piece of code or not” with 100% certainty. Thus turbofish was added to resolve the use.
Note that that article is about grammar, not syntax. The compiler can reject syntactically invalid code with 100% certainty in finite time; but once you get into mono, then you enter the realm of undecidability. The same is true of Rust.
The problem with C++ is that to know whether foo<bar> is template foo with parameter bar or unfinisihed expression that needs baz to become foo<bar>baz (interpreted as (foo < bar) > baz one needs to know whether foo is a template or not. And because templates are not generics one may need to execute arbitrary code before one would know if quz::foo is template or not (thanks to the constexpr calculations).
Rust sidesteps all that madness by simply making foo::<bar> distinct from foo<bar>baz on the trivially verifyable level.
Right. But in Rust one may first parse the whole program, build an AST tree and then start “getting into mono”. Few ambiguities are carefully selected to make sure building AST nodes with mark “could be foo or bar” is feasible. But in C++, thanks to the lack of turbofish, you have no such luxury: you have to execute arbitrary C++ code during the constructions of AST!