Thanks for having a look and sharing your thoughts!
So if I understood you correctly, you propose to use () for indexing in addition to method calls and [] for generics?
There are multiple things at play here:
No language in existence has ever managed to make generics with <> work without painful hacks and workarounds. For some examples see
- Rust’s
::<>,
- Java’s syntax inconsistencies like
foo.<Bar>(...) and confusing stuff like new <Integer>MyClass<String>("a", 3), or
- C#'s approach of trying to parse stuff with unlimited look-ahead and then going back and rewriting the parse tree if the assumption turned out to be wrong.
If there was a way, I’m sure people would have found it by now. <> is “familiar”, but keep in mind that it was pretty much the least painful choice when C++ was created. I think a language designed from the ground up should not settle with “least painful” if there are better options.
I think it’s not a good proposal, as [] is almost universally used for indexing and slicing in other languages, and having buffer(10) will be quite confusing, as most of programmers will think about functions, not indexing.
Indexing can pretty much considered to be a function that takes some number and returns an element. (Similar with slicing.)
There is no useful distinction here, considering that [] does different things on [1,2,3] and &[1,2,3] to begin with, and with Index and IndexMut it’s not even true that [] is used for indexing or slicing – it can do whatever the author came up with.
On the other hand you have an example of a language that uses [] for generics and the gained consistency is staggering. It has none of the issues mentioned above. I can’t even remember anyone arguing that going back to <> would be a good idea – it just works, and the rule of "[] is for types, () is for values" is extremely intuitive for beginners.
Given that all the evidence is out there, I think it’s fair to say that [] for generics is superior on every measure except “similarity with C++ design decisions”.
If you are talking about self-less methods, then I kinda see the point, but I like the coherence which makes Foo.foo() and Foo::foo(bar) equivalent, i.e. . is a nice hint that self is implicitly used in a method call.
True, it’s a minor thing. :: just feels a bit heavy-weight considering how often it is used.
Closures could be made to look much closer to functions, but somehow aren’t.
Any concrete suggestions?
A good question! I think it’s hard to figure out the best approach, because I don’t really like the -> syntax for the result type of a function to begin with. I would have understood why -> was chosen if closures then adopted similar syntax like params -> body, but they didn’t.
The -> also moves the result type closer to the function body which (at least for me) caused some confusion when reading code. E. g. I wondered why something like Option<Foo> { something_that_returns_option() } didn’t result in something like Option<Option<Foo>> until I realized that the Option was part of the signature, not part of the body.
The unneeded naming explicitness makes code harder to write and a little to read.
I don’t think explicit naming is unneeded – especially not in a language where you can rename everything during use anyway. One of the core issues with abbreviations is that there is often only one non-abbreviated way to write things, but multiple ways to abbreviate things.
I personally don’t see a problem with it. And I would’ve passionately hated CamelCase for method and function names.
No, they could have used lowerCamelCase. One of the negative effects snake_case is that it creates additional overhead for function names with more than one word and therefore tilts the scale in favor of single-word names, even if a two- or three-word name would have been more expressive.
Choice of prefix or postfix mostly depends on how nice it will be to read. For example listed methods can be read as “iterate”, “iterate mutably” and “convert into iterator”, while iter_into() would’ve been confusing. Is it “iterate into something” or what?
I think consistency and ease of use is way more important. Many people, especially beginners rely on auto-completion to discover related methods if they encounter an unknown library, so similar functions should end up being grouped together.
And would’ve been a disaster for those who writes code. It’s quite important to see documentation when you work with source code, and no, IDE is not an answer.
I don’t think it would be much of a deal, it would even make it easier to have two windows side-by-side, one with the code you are currently working with, and one with the documentation (which you then could also read nicely formatted in a browser or some markdown reader because it’s just some bog-standard markdown).
Can you elaborate?
Commenting a method without explicitly also commenting the doc comment causes the compiler to error out, because it can’t find the declaration to which that doc comment belongs.
How special syntax results in “abusion”?
Because macros require special syntax at the call-site (!) there is pretty much zero encouragement for macro authors to design macros in a way that minimizes/eliminates surprises. They can just say "but you knew what you got yourself into because of the !" and carry on.
I would have preferred it if the behavior of macros would have been the responsibility of the macro author, with the general rule “if the user needs to know that something is a macro, your macro is wrong”. The current situation just punishes reasonable macro authors that write macros with predictable behavior at the expense of authors that want to abuse the hell out of it.
I don’t see a connection between “over-used”
The lack of var-args (I get it, every language designer hates var-args) just led to plenty of macros that do nothing more that emulate var-args with macros (look no further than vec!). I think this is not a good situation to be in.
How do you propose to implement it instead? Don’t forget about formatting string checks.
At the very least I would have expected that println! and format! would have allowed to directly refer to the interpolated values: println!("{name} is {height} tall") instead of println!("{} is {} tall", height, name). The current API is very inergonomic and error-prone.
Other languages even combine this with formatting instructions and it works well, e. g. println(f"{name%s} is {height%2.2f} tall")
Hope this clears some things up!