> some systems, such as Linux, won't fail a memory allocation but instead crash you when you try to use it

False.

1) You can disable overcommit, and there are many of us that do this as a matter of course on all our Linux servers.

2) malloc can fail because of process limits (e.g. setrlimit or cgroups).

I don't program in C++, but I do use RAII-like patterns in C. By that I mean that when I create and initialize objects, all the necessary internal resources--particularly those that rely on dynamic allocation--are also created and initialized in the same routine.

That means most places where memory allocation can fail are grouped closely together into a handful of initialization routines, and the places where allocation failure results in unwinding of a logical task are even fewer. (While C doesn't automatically release resources, following an RAII-like pattern means deallocations are just as structured and orderly as allocations.)

I can understand bailing on allocation failure in scripting languages--not only is there much more dynamic allocation taking place, but allocation happens piece-meal all over the program, and often in a very unstructured manner (strings of variably length generated all over the place). Furthermore, often script execution occurs in a context that that can be isolated and therefore unwound at that interface boundary--i.e. a C- or C++-based service executing a script to handle a transaction.

But in languages like C++ and Rust, especially for infrastructure software and libraries, it's a sin IMO. These are languages intended for use in situations where you can carefully structure your code, they make it trivial to minimize the number of allocations (because POD-oriented), and they permit one to group and isolate allocations in ways (e.g. RAII) that make it practical (if not trivial) to unwind and recover program state.[1]

But why even bother?

1) Because these languages are often used in situations where failure matters. A core piece of system software that fails on malloc is a core piece of system software that is unreliable, and programs that rely on you can behave in unpredictable and even insecure ways.

1.a) Go authors take the view that reliability comes from running multiple instances in the cloud. Yes, that's one way, but it's not the only way, not always an option, and in any event anybody with enough experience dealing with "glitches" in cloud services understands that at least in terms of QoS there's no substitute for well-written, reliable service instances.

1.b) Security. It's often trivial to create memory pressure on a box. OOM killers are notorious for killing random processes, and even without overcommit the order of allocations across all processes is non-deterministic. Therefore, not handling OOM gives attackers a way to selectively kill critical services on a box. Disruption of core services can tickle bugs across the system.

2) Overcommit is an evil all its own. It leads to the equivalent of buffer bloat. Overcommit makes it difficult if not impossible to respond to memory resource backpressure. This leads to reliance on magic numbers and hand-tweaking various sorts of internal limits of programs. We've come full circle to the 1980s where enterprise software no longer scales automatically (which for a brief period in the late 1990s early 2000s was a real goal, often achieved), but instead requires lots of knob turning to become minimally reliable. Nobody questions this anymore. (Ironically, Linux helped lead the way to knob-free enterprise operating systems by making kernel data structures like process tables and file descriptor tables fully dynamic rather than statically sized at compile or boot time, so the kernel automatically scaled from PCs to huge servers without requiring a sysadmin to tweak this stuff. Notably, Linux doesn't just crash if it can't allocate a new file descriptor entry, nor do most programs immediately crash when open fails. Even more ironically, overcommit on Linux was originally justified to support old software which preallocated huge buffers; but such software was written in an era where sysadmins were expected to tailor hardware and system resources to the software application. Overcommit has had perpetuated the original sin.)

Not all software needs to handle OOM. Not event all C, C++, or Rust components. But for infrastructure software OOM should be handled no different than file or network errors--subcomponents should be capable of maintaining consistent state and bubbling the error upward to let the larger application make the decision. And if you're not writing critical infrastructure software, why are you using these languages anyhow?[2] If a language or framework doesn't permit components to do this, then they're fundamentally flawed, at least in so far as they're used (directly or indirectly) for critical services. You wouldn't expect the Linux kernel to panic on OOM (although some poorly written parts will, causing no end up headaches). You wouldn't expect libc to panic on OOM. There's no categorical dividing line beyond which developers are excused from caring about such issues.

[1] Granted, Rust is a little more difficult as it's hostile to allocation-free linked-lists and trees, such as in BSD <sys/queue.h> and <sys/tree.h>. Hash tables require [potential] insertion-time allocation. Still, it's not insurmountable. Many forms of dynamic allocation that can't be rolled into a task-specific initialization phase, such as buffers and caches, are colocated with other operations, like file operations, which already must handle spurious runtime failures, and so the failure paths can be shared.

[2] Maybe for performance? Performance critical tasks are easily farmed out to libraries, libraries are particularly suited to handling OOM gracefully by unwinding state up to the interface boundary.