Off-hand I don't know of any that make this ergonomic, at least none that don't make use of exceptions. Rust isn't unique in this regard.
What makes it a potential impediment in Rust is that the constraints and burdens of the borrow checker are offset by mechanisms like Box. The fact that all the extant examples choose the convenience of Box over handling OOM, even in situations where not handling OOM is obviously a deal breaker for production systems, speaks volumes about the significance of the problem and how the language shapes people's choices. Async/await is in the same boat--technically doesn't require a non-failing heap allocation, but who's going to bother making it work? You technically don't need async/await to do asynchronous programming, either, but the whole point was that this is the type of thing that needs to be addressed by the core language with some primitive (i.e. generators) that does the heavy lifting and which can be built upon.
I don't know enough Rust to know how easy it would be to create a Box-like implementation that rewrites the AST to automatically propagate allocation failure via the idiomatic Result<T, E> protocol. But that seems roughly what the proper solution might look like in Rust; either that or finally getting over the anxiety and aversion about exceptions.
Lua handles OOM quite well. Lua doesn't have try/catch, just "protected calls" which are not as light-weight as regular function calls. (A pcall initializes a recovery point with _setjmp.) In Lua you tend to use protected calls at, effectively, transactional boundaries--the points in your call graph where you're willing and able to rollback application state for non-specific, otherwise unrecoverable errors. AFAIU you could technically do the same in Rust, except Rust makes unwinding optional at compile-time[1], so it's not the kind of thing people will make a habit of deliberately designing for in their libraries. Which makes me think the likely solution for Rust, if any, is to permit libraries to opt-in to OOM recovery with an allocator pragma that does something like the Result<T, E> propagation mentioned above.
[1] Lua is GC'd. The cost of running destructors outside the normal call/return protocol is fixed and independent of whether an application uses protected calls.
What makes it a potential impediment in Rust is that the constraints and burdens of the borrow checker are offset by mechanisms like Box. The fact that all the extant examples choose the convenience of Box over handling OOM, even in situations where not handling OOM is obviously a deal breaker for production systems, speaks volumes about the significance of the problem and how the language shapes people's choices. Async/await is in the same boat--technically doesn't require a non-failing heap allocation, but who's going to bother making it work? You technically don't need async/await to do asynchronous programming, either, but the whole point was that this is the type of thing that needs to be addressed by the core language with some primitive (i.e. generators) that does the heavy lifting and which can be built upon.
I don't know enough Rust to know how easy it would be to create a Box-like implementation that rewrites the AST to automatically propagate allocation failure via the idiomatic Result<T, E> protocol. But that seems roughly what the proper solution might look like in Rust; either that or finally getting over the anxiety and aversion about exceptions.
Lua handles OOM quite well. Lua doesn't have try/catch, just "protected calls" which are not as light-weight as regular function calls. (A pcall initializes a recovery point with _setjmp.) In Lua you tend to use protected calls at, effectively, transactional boundaries--the points in your call graph where you're willing and able to rollback application state for non-specific, otherwise unrecoverable errors. AFAIU you could technically do the same in Rust, except Rust makes unwinding optional at compile-time[1], so it's not the kind of thing people will make a habit of deliberately designing for in their libraries. Which makes me think the likely solution for Rust, if any, is to permit libraries to opt-in to OOM recovery with an allocator pragma that does something like the Result<T, E> propagation mentioned above.
[1] Lua is GC'd. The cost of running destructors outside the normal call/return protocol is fixed and independent of whether an application uses protected calls.