Vehemently disagree on both
There seems to be some agreement that C++ overdid it. Both Go and Rust have more limited generic systems.
error handling needing to be designed in
I'm not expressing an opinion on return values vs exceptions. But some kind of standard is needed or library error reporting is terrible.
For example, if you look at what type traits are used for, it's essentially making up for the fact that C++ lacked semantics for compile-time checks of generic arguments, forcing anyone that needed to care about that to implement their own (often simple, special case) constraint solvers. Meanwhile, typeclasses in other languages wrote a generic constraint solver (ironic) and support the semantics to express those constraints on generic types, obviating the need for any kind of complex compile-time template logic hackery. Essentially the lack of semantics for a simple concept (yet difficult implementation behind it) forced the implementation of a simple extension to the same syntax that enabled really complex macro programming using the same templating engines.
It's no surprise that metaprogramming in C++ is exceptionally weird; it supports neither proper macros or generic programming, yet half-implemented both through templates to solve the lack of either.
Fast builds would be high on the list of my own "dream language" features. Long developer iteration times are poison.
That should have been buried decades ago and replaced by modules.
They're slowly layering in those ergonomics, which will eventually be nice, though there'll always be a big pile of legacy.
I'd love to know if you write lots of code and/or have to maintain other people's hugely templated code. I find if you have to do lots of the second you quickly stop writing it.
I don’t think anyone same would ever design templates today.
When? And for whom? Having to make everything a fixed size array like in C is no good either, that's for sure. It leads to bad guesses and comments like "should be big enough".
So, better just to carry the length along with the pointer (Rust calls these "fat" pointers) and use that as the source of truth about the array length (for dynamically sized arrays, such as those created by malloc).
1. Array types of fixed length, ensured by the type system. This corresponds to arrays in C, C++, and Rust, and fixed-size buffers in C#.
2. Array types of dynamic length, in which the length is immutable after creation. This corresponds to arrays in C# and Java, and boxed slices in Rust.
3. Array types of dynamic length which can be resized at runtime. This corresponds to vectors in C++ and Rust, and lists in C#, Java, and just about every interpreted language.
Of course, for FFI purposes, it is often acceptable to pass arrays as a simple pointer + length pair, this being the common denominator of sequential arrays across low-level languages.
I've also taken a look at Golang's slices, which have rather confusing ownership semantics. One can take a subslice of a slice, and its elements will track those of the original, but appending to that subslice will copy the values into an entirely new array. In fact, appending to the original slice past its capacity causes a reallocation, which can invalidate any preexisting subslices. This also occurs with C++ vectors and spans, if I am not mistaken. This is an area where I think Rust's borrow checker really shines; it prevents you from resizing a vector if there are any active slices, encouraging you to instead store a pair of indices or some other self-contained representation.
If you can establish the precondition that the underlying vector will not re-allocate as a result of the append, then it is perfectly safe to perform such an append while holding a reference to one or more elements of the vector. Same thing for insertions: references to elements before the insertion point may still remain valid if you can establish the precondition that the vector will not be resized. In both cases, it is straightforward to establish the precondition through the reserve() member plus some foreknowledge of how much extra capacity the algorithm needs.
You can always construct a user-defined reference type which back-doors the borrow checker, such as by storing indexes instead of iterators as you mentioned. If the std.vector is reduced, then they are still just as invalid.
I think it's also okay to address memory corruption using tooling (like how sel4 proof checks c), the language might not have to address it, but it would be nice for the language to make it easy to address.
* Null-terminated strings were a really bad idea.
* Arrays of ambiguous size were a really bad idea.
* Multiple inheritance just gets too confusing.
* Immutability is very useful, but not everything can be immutable.
* If thing A over here and thing B way over there have to agree, that has to be checked at compile time, or someday, someone will break that invariant during maintenance.
* Concurrency has to be addressed at the language level, not the OS level.
* Which thread owns what data is a big deal, both for safety and performance reasons, and languages need to address that. Some data may be shared, and that data needs to be identified. This becomes more important as we have more CPUs that sort of share memory.
* The jury is still out on "async". Much of the motivation is web services holding open sessions with a huge number of clients. This is a special case but a big market.
* Running code through a standard formatter regularly is a win.
* Generics can get too complicated all too easily.
* There are three big questions that cause memory corruption: Who owns it, who can delete it, and who locks it? C addresses none of those issues, GC languages address the second, and Rust (and Erlang?) address all three. Future languages must address all three.
* Error handling has to be designed in.