7

The documentation of std::process::exit says:

If a clean shutdown is needed it is recommended to only call this function at a known point where there are no more destructors left to run.

Maybe due to my lack of systems programming background, I have no clue if there are destructors left to run at a specific point and if I should care. The only thing that comes to my mind is pending write operations to a file (or to something else) where it's a good idea to leave things in a clean state.

Anything else to watch out for? I suspect it's not advisable to use in larger, more complex programs, but for small tools it seems convenient.

Thomas W
  • 14,757
  • 6
  • 48
  • 67
  • I wouldn't worry about it if you don't handle any sensitive data or connections. You can see the remarks in http://stackoverflow.com/a/21570851/1870153. – ljedrz Aug 30 '16 at 13:23

1 Answers1

9

Short answer:

  • rather use panic!() in nearly all cases
  • you can pretty much only be sure that no destructors are left to run, if ...
    • ... you are in the main() function
    • ... you manually handle the stack including unwinding (you're probably not...)
  • sometimes you can just ignore destructors when exiting the program anyway, but you need to be careful!

Slightly longer explanation

[...] for small tools it seems convenient.

If you want to exit your program due to an unrecoverable error, the recommend way is to panic!(). This will unwind the stack (run all destructors) and exit the program with additional information (including the message string you can specify).

[...] if there are destructors left to run at a specific point and if I should care.

There are destructors left to run if there are local variables that implement Drop in your current stack frame or in any stack frame above. A stack frame is a region in the stack memory that holds all local variable of a function call (loosely speaking). After a function exits, all local variables discarded which includes calling the destructor of all variables that implement Drop.

The probability that some destructor needs to run grows with the depth of your stack ("how many functions were called between main() and your current stack frame). Therefore it's really hard to reason about this whenever you are not in the main() function.


When do types implement Drop? When it's wrong to just ignore them. Consider an i32: when we exit the function we can just... leave it in stack memory, because it doesn't have any negative side effects (ignoring the special case of data security for a moment). However, there are many types that do need to implement Drop. Here are a few categories:

  • Allocating heap memory: Box<T>, Vec<T>, HashMap<T>, ...
  • Holding OS resources: File, Socket, ...
  • Returning handles: Ref, MutexGuard, ...
  • ...

Not running the destructor for those has different effects. The first group is probably the most harmless one: we will leak memory. But when you exit your program, the operating system will clean up all this memory anyway. Nearly the same goes for OS resources: file descriptor are usually deleted by the operating system when a program exits.

But there are more reasons to run a destructor. The important point to consider: you often don't know why certain types implement Drop, but these types do rely on it. Certain unwanted things can happen when you ignore this. Often you won't really notice, but sometimes it can lead to serious problems.

mcarton
  • 27,633
  • 5
  • 85
  • 95
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 1
    An extremely thorough and enlightening answer! I've been using `panic!()` in different places when I found a syntax error in a file I'm parsing, but I'd like to avoid the `Thread 'main' panicked...` noise and only present information about the problem I found. If all I have in my stack frames are simple data types like strings, chars, integers, a file that I'm only reading from, simple enums and structs I defined myself, I almost dare to leave the cleanup to the OS. – Thomas W Aug 30 '16 at 14:06
  • 3
    @ThomasW use a `Result` to bubble non-programmer-logic errors back up to `main`, then use `std::process::exit` at the end of `main` if you want to control the exit code to the operating system. Use `panic!` for program-logic-errors. – Shepmaster Aug 30 '16 at 14:11
  • 1
    @ThomasW What Shepmaster said. I think you should read [the "Error Handling" chapter in the Rust book](https://doc.rust-lang.org/book/error-handling.html). Also: the `String` type sometimes feels "simple" but it's super complicated (UTF8/Unicode) and also implements `Drop` because the string buffer lives on the heap. – Lukas Kalbertodt Aug 30 '16 at 15:35
  • @LukasKalbertodt I see the point about strings, but are there any dangers in letting the OS clean up their memory? I understood that `Result` is the cleanest way of handling this kind of errors. (Unless laziness kicks in, because that bubbling doesn't happen all by itself and needs to be implemented.) – Thomas W Aug 30 '16 at 16:10
  • 1
    @ThomasW No, the OS can clean up heap allocated memory without a problem. I wanted to draw attention to all other reasons for destructors that the OS may not be able to handle as well. And about laziness: unless already done, read the chapter. The Rust error handling is surprisingly elegant and often short. `try!()` and other tricks make this possible. – Lukas Kalbertodt Aug 30 '16 at 16:35
  • @LukasKalbertodt In spite of the inner Schweinehund, I'll definitely read the error handling chapter. – Thomas W Aug 30 '16 at 21:31
  • @ThomasW memory and file handles are easy things for the OS to handle (no pun intended). What's not handled by the OS are destructors that do things like finish writing buffered data to a file to leave it in a consistent state, for example. – Shepmaster Aug 30 '16 at 23:11