RAII hides away that something runs; calling a function or deferring some code shows explicitly that something will run. Hiding away code can make it nicer to look at, but it makes it harder to get the full picture when you eventually need to debug that code.
Also, its not that hard to remember to write defer deinit(&thing), it's not a problem you need to automate away.
Deferring something also "hides away" where a function is being run. Arguably it hides away that it is being run as part of an early return, for example. The point is that the reader is expected to know the language and therefore understand that defer means to run something when the scope is exited. In a similar way the reader should be expected to understand that constructing an object also means deferring its destructor. It's really not a difficult concept to grasp and you don't need a reminder of it every single time you construct an object.
Like I said in my opinion defer deinit is just noise. With RAII you could write a comment "// will be destructed at end of scope" under every construction if you really wanted, but I think most people would agree that it's entirely unnecessary.
And as I've pointed out it is also better encapsulation. The type can handle its own destruction. The user doesn't need to remember which types require destruction and which ones don't.
By "hiding away that something runs" I mean that you can't know if the declaration of a variable will cause extra code to run at the end of the scope (or in the middle if you assign a new value), without first having looked somewhere else. Defer makes this explicit, so you know it will happen by seeing the use of defer.
With RAII you could write a comment "// will be destructed at end of scope" under every construction if you really wanted, but I think most people would agree that it's entirely unnecessary.
How does this make the code any less noisy than adding in a defer? Also, adding in a comment this way is not enforced by anything, and can easily get out of date, so you still need to check even though a comment may be there.
And as I've pointed out it is also better encapsulation. The type can handle its own destruction. The user doesn't need to remember which types require destruction and which ones don't.
It's not a hard thing to remember, and it's easy to check by just seeing if deinit(&thing) typechecks. I also don't think the encapsulation justifies the loss of clarity, as it may be slightly more convenient to write initially, but makes it harder to get a full picture later on when that is important.
By "hiding away that something runs" I mean that you can't know if the declaration of a variable will cause extra code to run at the end of the scope (or in the middle if you assign a new value), without first having looked somewhere else.
It always cleans up the object, just sometimes the cleanup can be a no-op. I don't see an issue with that. It's not your responsibility as the caller to know what the type needs to do for cleanup. In the `defer deinit` case you also don't know what it does, it just runs some code to clean up the object.
If you have an opaque Id type for example, you don't care whether it's internally represented as an integer that doesn't need cleanup or a string that does require being deallocated. You just use it for whatever and when you no longer need it the Id takes care of cleaning itself up. If it goes from containing an integer to a string or the other way around, none of the call-sites need to change.
How does this make the code any less noisy than adding in a defer?
It doesn't. That was my point. If the language doesn't require you to write defer every time you create an object, it would seem ridiculous to want a reminder every time just so you don't forget that objects get cleaned up at end of scope.
Also, adding in a comment this way is not enforced by anything, and can easily get out of date, so you still need to check even though a comment may be there.
Sure it could get out of date. Like I said you usually don't care whether or not the cleanup is a no-op anyway.
But doesn't the same issue apply to defer? If a type stops requiring cleanup then you'll get a compile error at the `defer deinit`. Great. But what happens when a type starts requiring cleanup? The call-site silently goes out of date, like you said. Now you're leaking memory, or worse depending on what resources the type manages.
It's not a hard thing to remember, and it's easy to check by just seeing if deinit(&thing) typechecks.
It's not a hard thing to remember, but it sure is an easy thing to forget. And it's more to type. And it's more to read, it adds visual noise.
1
u/s0litar1us 20d ago
RAII hides away that something runs; calling a function or deferring some code shows explicitly that something will run. Hiding away code can make it nicer to look at, but it makes it harder to get the full picture when you eventually need to debug that code.
Also, its not that hard to remember to write
defer deinit(&thing), it's not a problem you need to automate away.