4 ms·
RAII has the advantage that you can't forget to do it, but defer has the advantage that you can handle failure in ways other than panicking. Of course, in many
by kbolino 2mo ago
RAII has the advantage that you can't forget to do it, but defer has the advantage that you can handle failure in ways other than panicking. Of course, in many cases (e.g. closing a file), there's generally not much you can do anyway even if you want to handle the error directly, but at least it's possible.
- bewareofscams 2mo agoRust can handle that with RAII without panicking just fine. And not only Rust..
- kbolino 2mo agoSure, you can just ignore errors entirely, and this is what Rust actually does today, at least for std::fs::File. The only other option within RAII that I can think of is that you can mutate some external, longer-lived state. The primary way to deal with error-on-clean-up in RAII languages is to not rely exclusively on RAII for it. Rust's File type, for example, has sync_data and sync_all methods (which, to be fair, only even need to be called for writable file handles). I don't think there's anything wrong with this approach, but it ends up being just as explicit and therefore forgettable as defer. It should be noted that you can (at least in Rust) actually implement defer using RAII; see e.g. the scopeguard crate. Since RAII is block-scoped, this defer is also block-scoped (like Zig) rather than function-scoped (like Go).
- throwaway892654 2mo agoRust uses affine types, which means that the compiler guarantees that you clean resources (call the destructor) either zero, or one time. If you call it zero times, then the compiler inserts the call to the destructor for you, in which case there is no opportunity to handle errors in the cleanup, so the result is they are ignored (or you get some kind of panic) A system that is based on linear types would have an advantage here. In a linear type system, the compiler guarantees that you always call the cleanup function (destructor) exactly once. With such a system, you can have the destructor return an error result, and since the call will always be explicitly written in the code (rather than generated automatically by the compiler), there will always be an explicit errorn handling code branch.
- wasmperson 2mo ago> (e.g. closing a file), there's generally not much you can do anyway If closing a file fails then you treat it the same as how you would treat a write failure: int err = 1; FILE *f = fopen("whatever.txt", "w"); if(f){ if(5 == fwrite("Hello", 1, 5, f)) err = 0; if(0 != fclose(f)) err = 1; } return err; Code which writes to files and doesn't check for errors on close is subtly incorrect, although my understanding is that kernel devs bend over backwards to make failure unlikely, probably because everybody does it incorrectly anyway.
- tredre3 2mo agoNot that it matters, but fclose() doesn't happen in the kernel, so the kernel devs can't do anything about it. All libcs have essentially the same implementation: - is fp NULL or already already closed? return error - call fflush() and return error if it fails (fflush also happens in userland, it does a seek() then a write() of the userland buffer) - call close() and return error if it fails close() follows essentially the same process inside the kernel: check fd is valid, call flush() (this time truly to disk), close it.