Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Destructors aren't just good. They are one of the most important innovations in programming, since they reduce boilerplate and prevent many bugs. Developing a language without them means introducing more bugs which could be avoided.


It violates one of zigs principle of no hidden control flow


If exceptions count as hidden control flow because suddenly a function call can jump to a `catch` block, then defer absolutely counts as hidden control flow because suddenly the end of your function jumps to a `defer` block defined at an arbitrary point higher up. If Zig wanted to be consistent with its own philosophy, it would require some sort of keyword at the end of every scope to indicate that a defer was happening (and require multiple uses of the keyword to indicate multiple defers).


It's not the same, defer does not conditionally interrupt the flow of execution, it will always run at the end of a block. If I see a defer, I am absolutely certain that whatever is deferred will run. The same is not true for exceptions. If I am not mistaken defer just moves the code at the end of the block, no jumping is involved.


> It's not the same, defer does not conditionally interrupt the flow of execution, it will always run at the end of a block.

But destructors also don't conditionally interrupt the flow of execution, and always run at the end of a block.

> If I see a defer

The point is that you're not seeing it. In order to know if there's a defer happening at the end of a function you can't just read the end of the function, you need to read the entire function. That's non-local reasoning, which is what Zig professes to abhor.

And in fact defer is worse than destructors here, because a destructor runs the exact same code every time, whereas defer allows arbitrary code, so you need to review every single usage. And you also need to remember not to forget to use it in the first place (which is the classic footgun with defer), so in addition to vetting everywhere you use it, you also need to vet everywhere you might have forgotten to use it.


> But destructors also don't conditionally interrupt the flow of execution, and always run at the end of a block

Why bring up destructors, I was talking about exceptions. Destructors and exceptions are orthogonal concepts, one can be implemented independently of the other. I'm specifically referring to try-catch blocks like those in java.

Compare this:

  try { foo(); }
  catch { bar(); } 
to this:

  defer bar();
  foo();
In the first one, bar() may or may not run depending if there's an exception. In the second one, bar is guaranteed to run. Thus, it means defer does not conditionally interrupt the flow of execution.

> The point is that you're not seeing it. In order to know if there's a defer happening at the end of a function you can't just read the end of the function, you need to read the entire function.

What? You don't need to read the entire function, you only to scan or check for defers scoped in a block, or in some cases, just the top of a function or block. Wanting to just read the end of a function is unreliable anyway with the existence of early returns (which defer fixes by the way).

You could have made a more compelling (but not necessarily a valid) case by citing zig's try-catch, which makes me think that you are just arguing in abstract without actually having tried writing code that uses defer, or any zig code for that matter.


I cite destructors specifically because Zig denounces destructors as "hidden control flow" while presenting defer as a non-hidden alternative to destructors, which I find to be an incoherent philosophy.


But you were originally talking about exceptions. As I said, destructors and exceptions are distinct concepts, you can't just suddenly interchange those two terms as that would invalidate my whole disagreement, unless you wanted to shift the goal posts a bit to the side.


Honestly, I don't know if I'd argue on grounds of no hidden control flow, but rather on grounds of fine-grained deinitialization. If I allocate a bunch of data structures in an arena, I don't want their destructors to be run, because I'm managing the memory a different way now. I suppose in Rust you could use `Box::leak` to prevent a destructor from running, but that's not exactly ergonomic (plus iirc you can't free a 'static reference safely). Especially if you've needed to tamper with a data structure's internals, you can't use the normal deinit functions.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: