4 ms·
It is indeed unfortunate Rust settled on <> for generics. Looking back, [] is obviously a superior choice. But alas, that train passed.
by sanxiyn 6y ago
It is indeed unfortunate Rust settled on <> for generics. Looking back, [] is obviously a superior choice. But alas, that train passed.
- qalmakka 6y agoUsing `<` and `>` for generics is not only bad due to their primary meaning as comparison operators, but also due to having to special handle `>>` in the lexer. It took almost 30 years before the C++ standard supported expressions like `std::vector<std::vector<int>>`. For some reason, Bjarne Stroustrup managed to pick the only two character in C that could be merged into a different lexeme.
- chrismorgan 6y agoYet [] still has the same problem while you use [] for indexing: fn a[T](_: T) -> &'static str { "type" } struct b; fn ambiguous() -> &'static str { let a = [|_: b| "expression"]; let b = 0; let c = b; a[b](c) // "type" or "expression"? } So you very probably want to unify indexing and function-calling, which becomes very difficult for Rust because of the whole lvalue/rvalue situation. Because manual implementation of the Fn traits is unstable and no generic implementations exist, I do actually think that this could technically be done (a new edition could go to [] for generics sans turbofish), but it covers a lot of other hazardous ground too (e.g. adding maybe-uninitialised reference types) so that it couldn’t be done for still some years, and I doubt very much that it ever will be (because I doubt many people think it would be worthwhile even if it was free to do, let alone when there’s major implementation cost). I and a few others did try a bit to convince people to shift to square bracket generics in late 2013–mid‐2014, but failed. Certainly the language was in no shape to unify indexing and calling (the best I could come up with was to keep them separate for now, just using the same syntax with the compiler selecting between the mutually-exclusive implementations, with the potential for merging the two in future), so at that time it would probably have retained the turbofish, which didn’t help with convincing people of the superiority of square brackets (no compelling benefit—switching to something unfamiliar, and remember Rust’s weirdness budget was already about expended, without fixing what many felt was the biggest problem with angle brackets).
- masklinn 6y ago> Yet [] still has the same problem while you use [] for indexing That’s not entirely true: with [] your parse is unambiguous, the node contains everything up to the matching ], doesn’t matter whether it’s an index, a literal, a generic, … the resolution of what’s what requires more work, but you can at least build your tree. With <> however you can’t even parse without knowing whether < is a comparison operator because the knowledge of where its rhs ends depends on that information. This issue is compounded by the right-shift problem.
- chrismorgan 6y agoOK, perhaps not quite the same problem, because the hostility of angle bracket delimiters to the token tree parsing approach is definitely a real pain point, but it still has the problem of ambiguity, which is anathema to Rust even if it could be resolved by knowledge of the items in the current scope (which I don’t think it could be). Inside the square brackets, you don’t know whether you’re parsing an expression or a type, and that matters. If anything, I’d say that this is much more trivially problematic than angle brackets, because on angle brackets you have to write quite convoluted code before the actual ambiguity arises; with square brackets, syntactic ambiguity arises much more easily and commonly—I can think immediately of a few examples of code that I’ve seen or written that would be ambiguous.
- masklinn 6y agoI guess you’re referring to rust having separate value and type namespaces, so you could fairly easily get into a situation which would be rather hard to disambiguate?
- chrismorgan 6y agoRust very deliberately treats parsing as an isolated step in compilation. The ASTs of {function + type parameter + arguments} and {value + indexing + call} are very different. At parse time, you don’t know what types and values are in scope—they’re resolved later. Some languages have been willing to compromise on this (C and C++ certainly do, and Perl is famously unparseable), but it always causes a great deal of pain, as you can’t build any sort of isolated tooling around the language any more, but must use an actual compiler to even try to parse the thing properly if you want to be robust; static analysis is thus made more difficult, especially as you must now look at entire projects and can’t properly look at individual files—and worse, you must be able to compile the project to be able to analyse it. So Rust has deliberately gone the other way: you can parse the language easily and completely, so any form of static analysis doesn’t need a real compiler to back it and can act on files in isolation. In short, Rust is philosophically obdurately opposed to needing to do disambiguation in the parsing phase. So it’s actually not really about the actual ambiguity, which might not even be a thing—I can’t remember whether {function + type parameter} would be looking up in the type or value namespaces, so my example might actually be wrong there—but rather that you can devise supporting code that would seem to make `a[b](c)` mean two different things, and that’s untenable for Rust, because it refuses to let scope contents influence the parse.
- db48x 6y agoClearly the superior choice would have been ⸨⸩, as in foo⸨a⸩(b).