The decision was made with pragmatism in mind. What do most programs in Rust want to do? Most of them are fine with panic on an error. Making your code robust against this, at least in Rust, makes things a lot more cumbersome, and most programs would panic upon such an error anyway. Most programs that care about this possibility aren't using the standard library anyway. Therefore, for the standard library, the decision was made to panic on OOM, and to not block the release of Rust 1.0 on something that, at the time, wasn't itself a stable feature.
Three years later, I'm comfortable with this decision; three more years of waiting on Rust 1.0 would have killed it as a language. Many, many useful programs have been written in Rust since then.
Of course, you can disagree with this decision; I mostly want to communicate that it was not a flippant "who needs to care about that," but a careful decision made after weighing the tradeoffs. In the old days, those tradeoffs were very different.
It seems like a weird choice to me. Zig's standard library functions that require allocation take the allocator as a parameter, and return oom errors back up to the caller to do with as they please. Usually, the caller will also just kick that error up until it reaches the top of the call stack and gets caught by the default panic handler, but at any point they can choose to handle it however they want. Handling the error by kicking it up the call stack is a single keyword at the callsite.
As a bonus, this means that when you're reading code you can easily spot the function calls that have a possibility of failing.
Yeah, I specifically added the "in Rust" here because I know Zig really cares about this problem. I haven't actually used contemporary Zig enough to be able to say anything about its approach, though.
> Zig's standard library functions that require allocation take the allocator as a parameter, and return oom errors back up to the caller to do with as they please.
You could do something like this in Rust. Here's a function that accepts a vector and pushes something onto it:
Does the data structure hold the allocator, or the function? What if I created a list with one allocator, but called push_back with another? It'd have to be in the structure, right? Given that, here's what this feature would look like, roughly, if Vec<T> supported it:
In summary, functions that can fail have an inferred error set, or an explicit one[2]. At the callsite of functions that can fail will either be `if`, `while`, `catch`, or `try`, in order to deal with the error case.
Neat! So yeah, reducing Result to ! and inferring errors makes this way less boiler-plate-y. One of Rust's core design decisions is that we should never do type inference for function definitions, so we can't do this.
And yeah, if you hold a pointer to the allocator instead of parameterizing by it, that helps too. With that in mind,
In today's Rust, which is sort of what already happens; a given instance can't change the allocator, but refers to the global one. I don't have a great handle on the tradeoffs from keeping a pointer vs parameterization.
Anyway, yeah, if we allowed for inferring the error type, that would make this almost the same as Zig here. Stealing your syntax:
Same thing as everyone else: play russian roulette with the OOM-killer and hope that whatever was important to the user didn't get destroyed. Can't really do anything about the OS lying to you.
Three years later, I'm comfortable with this decision; three more years of waiting on Rust 1.0 would have killed it as a language. Many, many useful programs have been written in Rust since then.
Of course, you can disagree with this decision; I mostly want to communicate that it was not a flippant "who needs to care about that," but a careful decision made after weighing the tradeoffs. In the old days, those tradeoffs were very different.