Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't `Box::new` return an `Option` or `Result`?

I don't understand why Box::new doesn't return an Option or Result.

The allocation can fail because memory is not unlimited, or something else could happen; what is the behavior in such cases? I can't find any information about it.

like image 630
Stargateur Avatar asked Jul 05 '17 10:07

Stargateur


3 Answers

A more general form is What to do on Out Of Memory (OOM)?

There are many difficulties in handling OOM:

  • detecting it,
  • recovering from it (gracefully),
  • integrating it in the language (gracefully).

The first issue is detecting it. Many OSes today will, by default, use swap space. In this case, your process is actually in trouble way before you get to the OOM situation because starting to use swap space will significantly slow down the process. Other OSes will kill low-priority processes when a higher process requires more memory (OOM killer), or promise more memory than they currently have in the hope it will not be used or will be available by the time it is necessary (overcommit), etc...

The second issue is recovering. At the process level, the only way to recover is to free memory... without allocating any in the mean time. This is not as easy as it sounds, for example there is no guarantee that panicking and unwinding will not require allocating memory (for example, the act of storing a panic message could allocate if done carelessly). This is why the current rustc runtime aborts by default on OOM.

The third issue is language integration: memory allocations are everywhere. Any use of Box, Vec, String, etc... So, if you shun the panic route and use the Result route instead, you need to tweak nearly any mutating method signature to account for this kind of failure, and this will bubble in all interfaces.

Finally, it's notable that in domains where memory allocation failure need be handled... often times memory allocation is not allowed to start with. In critical embedded software, for example, all memory is allocated up-front and there is a proof that no more than what is allocated will be required.

This is important, because it means that there are very few situations where (1) dynamic memory allocation is allowed and (2) its failure must be handled gracefully by the process itself.

And at this point, one can only wonder how much complexity budget should be spent on this, and how much complexity this will push unto the 99% of programs which do not care.

like image 162
Matthieu M. Avatar answered Oct 19 '22 22:10

Matthieu M.


I found the following communication between the Rust developers regarding some of the lower-level functions in liballoc not returning Options: PR #14230.

Especially the following parts explain some of the reasons behind it:

huonw:

Hm... shouldn't the lowest level library not be triggering task failure? Are we planning to have any lower-level libraries returning Option or something?

alexcrichton:

I found that it was quite common to want to trigger task failure, much more so than I originally realized. I also found that all contexts have some form or notion of failure, although it's not always task failure.

huonw:

I was thinking from the perspective of task failure not being recoverable at the call site, i.e. a higher level library is free to fail, but the absolute lowest building blocks shouldn't, so that people can handle problems as they wish (even if it's just manually triggering task failure). If liballoc isn't designed to be the lowest level allocation library, failing is fine. (BTW, I think you may've misinterpreted my comment, because I wasn't talking about libcore, just liballoc.)

alexcrichton:

Oops, sorry! I believe that the core allocator interface (located in liballoc) will be specced to not fail!(), just the primitives on top of them (for example, the box operator).

Perhaps we could extend the box syntax to allow returning Option one day to accommodate this use case, because I'd definitely like to be able to re-use this code!

like image 12
ljedrz Avatar answered Oct 19 '22 22:10

ljedrz


This is a language design decision. You have to consider not just the logic of a single operation (Box::new, for example) but how it will affect the language ergonomics. If we were to handle the memory allocation errors with the Return mechanics then these errors would've started to bubble up pretty much everywhere. Even if the method doesn't allocate any memory on the heap currently, it might resort to it in the future. Suddenly a simple change in implementation would be stuck because you'd have to change the API, which with semantic versioning means a major release. All that for a little benefit, because the out of memory handling isn't very reliable or useful in the presence of swapping and memory killers (often you should stop allocating the memory long before you get an out of memory error).

The subject was much discussed on reddit.

One proposed solution I've seen is to treat the out of memory as a panic, unwinding and terminating the corresponding task.

like image 6
ArtemGr Avatar answered Oct 19 '22 22:10

ArtemGr