By expressivity I don't mean conrecte syntax, but the number of lines needed to express an algorithm (up to a small constant). C doesn't have C++'s expressivity because it lacks templates, and while you can sort of express that kind of thing with macros, it's not all the way there. Zig, however, gets you all the way (you might need to write matrix_plus instead of +, but the expressivity is the same).
Which seems to be something Zig can't do.
Hell, you can define operators for generic length SIMD lane.
Can I write my SIMD/number abstraction and have it work?
Let's say I need rotors. With six dimensions. Can I overload it's basic numeric operations?
A more humble critique would be to just acknowledge that you don’t seem to be a member of Zig’s target audience, and move on.
Saying "you don't want operator overloading" in such cases is a reductionist view of how systems programming should look.
The point is, it’s a tradeoff. With overloading, exactly which code executes on a specific line becomes a bit more implicit because it depends on extra information from the surrounding context: the types of the arguments.
That comes with the usual tradeoffs. On the one hand, you could argue that the compiler is taking care of it for you, which saves the programmer from having to manually deal with it, and removes some visual clutter from the code.
But you can also argue that letting the choice of function be implicit means that a programmer who wants/needs to keep track of exactly what every line of code is doing needs to carry all that contextual information in their own head, too. For this programmer, it’s adding to the mental burden.
I don’t think it’s a coincidence that Kelley constantly brings up how Zig was inspired by difficulties writing a digital audio workstation with very hard real-time requirements. Part of what he’s doing there is giving a motivating case for why he wants to be an absolute control freak about, e.g., micro-managing exactly when memory is allocated and deallocated. That’s something only a subset of systems programmers need, because not all systems programming is dealing with things like hard real-time.
I actually don't like Rust's choice to implement + for Strings but most people seem to accept that, however C++ goes ludicrously far beyond "I don't like it but some people do" and straight off the cliff into "Nobody thinks that's OK" leaving you to wonder why there was effort to implement this bad idea. For example C++ can overload the short-circuiting boolean operators... but the overloaded versions don't short circuit. So now you've made things dramatically worse!
This depends on how you're squinting at this word "resolution".
Remember on a modern CPU an XOR R1 R1 does not result in an ALU operation, the decoder understands this idiom and just renames a zero'd register as R1, whereas of course it can't do that for say XOR R2 R3 because that's an actual XOR operation. So if "resolution" means "exactly what happens on the CPU" apparently there is no such facility provided even in assembler.
So apparently we mean only that it should have, in some sense, the same effect. I think the fact Rust is OK with
assert!("Goose".contains('o'))
assert!("Goose".contains("Goo"))
assert!("Goose".contains(&['a', 'e', 'i', 'o', 'u']))
.. meets this criterion, whereas presumably you do not agree.For a somewhat different example of the same general idea, look at how Java handles primitives and their boxed counterparts. = on two ints always compares their value. = on two Integers compares their pointers. But it uses flyweight instances for values up to 256, so in Java, whether two Integers with the same numeric value are equal according to the = operator depends on what that value is.
Then throw autoboxing and generics into the mix, and it suddenly becomes very easy to do refactorings that work fine in the unit tests (which tend to use small numbers) but fails in confusing ways in production.
This isn’t an example of operator overloading, of course. But I hope it more simply illustrates why, at least in certain problem domains, there’s value in being able to visually tell when surprising runtime shenanigans are and are not possible. And also to make it impossible to cross that boundary without touching all code that falls within the blast radius of that change.
I believe Java caches Integer objects for values -128..+128, not 256. But that may be an implementation detail, and not something to count on.
We both misremembered. I found where this is documented and the range is -128..127 inclusive: <https://docs.oracle.com/en/java/javase/27/docs/api/java.base...>
Rust just stabilized its `Allocator` API in version 1.100.0. Definitely worth checking out!
Seems like it will be useful for use-cases like embedded where you're restricted to certain parts of the library anyway. But the battle is kind of already lost for support at the level of Zig or Odin. Even if they retrofitted support for the whole standard library, they'll never be able retrofit all crates or make people even bother adding support for all new crates.
The stdlib types that allocate have been generic (with default generic parameters) over the underlying Allocator used for a very long time. All widely-used data structure libraries where it's feasible/reasonable to use other allocators also are already generic over the Allocator.
It won't "take time to retrofit" because the language and ecosystem have already been prepared for it for 10 years. For context, async syntax was introduced a few years after that and if you just started learning the language today you wouldn't notice it wasn't there in 1.0.
> This release brings about the stabilization of the core parts of allocator_api, enabling library authors to define custom allocators that can be used together with certain standard library types - currently Vec and Box. These are enabled through the newly-stabilized Allocator trait.
That made it sound like it was rather limited currently.
I asked an LLM (because it's outside my expertise) and it said:
> It is fair to say that the heaviest architectural lifting is already done: adding a defaulted type parameter (A: Allocator = Global) preserves backward compatibility for existing code, and the core data structures already store and thread the allocator internally. However, plumbing that generic parameter through String, HashMap's std wrapper, and hundreds of standard library methods still represents substantial retrofitting work rather than a completed state.
Looks like The patch notes lead me a bit astray. But it's also not at an 'already all there' state either.
If you want to use your MyCustomAllocator with a HashMap or a BinaryHeap or you've lost your mind and want a LinkedList none of that was stabilized yet.
Personally I am slightly interested in HashMap, VecDeque and String but I think Vec is much more heavily used than any of those and will illustrate just how thin the pickings are in this space for real.
If there's a particular 3rd party collection you know would benefit, you could ask them, or write a PR to make use of Allocator. The standard library collections of course know people are interested, you can think of this as an MVP.
I just picked a few I personally want to see.
Odin in particular does a simply terrible job here. Take the most common need for allocations, a growable array type, in Rust Vec<T> and in Odin dynamic[T]
In Rust we only pay extra to store the allocator metadata if there is allocator metadata. If we're content using the general purpose allocator Global with global metadata it's free. In Odin we always need to pay to store an "allocator proc" function pointer and a (possibly unused) metadata pointer, so that's 2x machine word worse for Odin. We pay this even if we never allocate, and indeed even if we can't allocate, whereas of course in Rust that's free.
But then when we do need to allocate, in Rust that's a static dispatch unless our allocator specifically wants dynamic dispatch. So again for Global it's just a static dispatch. But in Odin it's always a slower dynamic dispatch.
The upside? To the extent there is one it's probably the New Jersey style, ie it was simpler to implement Odin. Which is a benefit to Ginger Bill, but not to you as an Odin programmer.
I have no practical Odin experience, I just looked at the core concepts. I tried a bit of Zig and there's significant discipline in the standard library to enable allocator use. But in 3rd Party libraries this discipline is hard to enforce because it also makes it harder to design easy to use interfaces.
I'm interested where on the spectrum of allocator usability Rust will end up at. I'd expect it somewhere between Zig and C++