Live data from Hacker News

Goto (2007)

beej.us

131–140 of 207 posts

Re: Goto (2007)

#131

Earlier quoted context omitted.

> and defer Which “defer”? The go panic/defer pair is, while structured differently, functionally equivalent to exceptions.

You use exceptions to clean up resources even when there are no errors? Why?

You don't need to use exceptions for that, but a finally block, something like this:

  try { ... }
  catch (SomeException e) { ... }
  catch (SomeOtherException e) { ... }
  finally { /* cleanup code */ }
Pretty much every language with exceptions has an equivalent of that finally block, a portion of code guaranteed to run whether there is an exception or not.

Defers in Go act like that finally block, but permit you to attach them at the point where the initialization they clean up was written. This can help with longer code blocks, but it's fundamentally the same as the above (with or without the catches). So if you use defer (the following is not really Go, but captures the idea):

  function echo_file (filename) {
    f = open(filename);
    if nil == f { return; } // simple error handling, just don't do anything
    defer close(f);
    for line in f {
      print(line);
    }
  }

  function echo_file (filename) {
    f = open(filename);
    if nil == f { return; }
    try {
      for line in f {
        print(line);
      }
    }
    finally {
      close(f);
    }
  }
The former is much shorter and clearer, especially for straightforward cleanup code, but is the same as the latter in what it does. Whether an exception or panic occurs during the read/print loop or not, the file will always be closed in both cases.

Re: Goto (2007)

#132

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

Yeah to those still adamant about it, if you've ever used a: - function call - if sentence - any kind of loop You've used a GOTO under the hood. I hope you can live with yourself, you monster :)

a case statement is the one where the GOTO peeks out the most, imo.

Re: Goto (2007)

#133

Earlier quoted context omitted.

Can you give an example of where you've used goto in C or a C-like language? Or a rough estimate of the number of times you've found goto to be the best solution? I agree with your post in principle, but in practice I can't think of a single example where I've used a goto that wasn't eventually refactored to something better that didn't have the goto. Edit: Probably the most common example (and one given early in the…

It's a very common pattern for C memory allocation checking. A public example I know off the top of my head can be seen here: https://github.com/zedshaw/learn-c-the-hard-way-lectures/blo... (the implementation of the CHECK macro). That's in a C tutorial but I've implemented a version of that macro frequently. Let's say you need to dynamically allocate two buffers in a function and want to make sure they are freed at…

You have "free(buf_2); // Safe if null", but if CHECK(buf_1) turns into a "goto error", won't buf_2 be uninitialized? And so can take on any value?

Re: Goto (2007)

#134
post #133

Earlier quoted context omitted.

It's a very common pattern for C memory allocation checking. A public example I know off the top of my head can be seen here: https://github.com/zedshaw/learn-c-the-hard-way-lectures/blo... (the implementation of the CHECK macro). That's in a C tutorial but I've implemented a version of that macro frequently. Let's say you need to dynamically allocate two buffers in a function and want to make sure they are freed at…

You have "free(buf_2); // Safe if null", but if CHECK(buf_1) turns into a "goto error", won't buf_2 be uninitialized? And so can take on any value?

You are correct, will edit. C is hard, writing C in the browser sans coffee is harder. :-)

Re: Goto (2007)

#135
post #115

Earlier quoted context omitted.

https://github.com/codr7/ampl/blob/main/src/ampl/eval.cpp

I'm sure there's probably a reason for doing things this way, but it seems like that code would make more sense as a function pointer table and a series of functions. Was that not preferred for aesthetic reasons or for some specific technical reason?

Speed.

Your suggestion adds indirect memory access plus function call, for every instruction.

I've tried every other way I can think of, and nothing runs faster from my experience.

Re: Goto (2007)

#137
post #91

Earlier quoted context omitted.

This is much more confusing to me than just using a goto. From your example, it's not immediately clear at all to me if you're breaking out of outer, or if you're breaking TO outer. You can figure it out with this simple example, but it's way more confusing than it needs to be. And importantly: way more confusing than just using goto. Given that break and continue are just gussied up gotos anyway, just put the label…

There is no confusion if you're already familiar with the anonymous break: // find in a loop for (a : array) { if (a == target) { print("Found it"); break; } } This breaks the loop. A named break is no different except that it names the loop that will be broken out of: find: for (a : array) { if (a == target) { print("Found it"); break find; } } Exactly the same as the previous, but we've named the loop for some reas…

The label should be at the end of the for block for no confusion. It just looks like the whole thing will be run again.

Re: Goto (2007)

#138

Earlier quoted context omitted.

There is no confusion if you're already familiar with the anonymous break: // find in a loop for (a : array) { if (a == target) { print("Found it"); break; } } This breaks the loop. A named break is no different except that it names the loop that will be broken out of: find: for (a : array) { if (a == target) { print("Found it"); break find; } } Exactly the same as the previous, but we've named the loop for some reas…

The label should be at the end of the for block for no confusion. It just looks like the whole thing will be run again.

If you expect break to go to the start of a loop, then you've confused it with continue.

Re: Goto (2007)

#139

Earlier quoted context omitted.

What is done is that people have taken the useful cases of goto, categorized them, and use renamed keywords for different cases. In many modern language, there are four keywords that all imply a general feature of goto: break (go to the end of the identified block), continue (go to the increment block of the identified loop), early return (go to the function cleanup code), and throw (go to something complicated). Ind…

I would disagree with defer being better than goto. Some people prefer it, but I think in a lot of ways it makes your control flow harder to see than just a few cleanup blocks at the end plus goto.

It's a tradeoff.

defer in Go is more like a try/finally statement in other languages, there's a guarantee that the deferred code will run even when there's a panic in the intervening code:

  handler = open(something);
  defer close(handler);
  // something causing a panic
Should be the same as:

  handler = open(something);
  try {
    // something causing an exception
  } finally {
    close(handler);
  }
This is not necessarily true of goto, you have to be more deliberate and disciplined (and discipline doesn't scale) to ensure that when your code hits an error you go to the cleanup section, if you miss even a single case and return early, you will not do the cleanup. Both defer and finally eliminate that potential error.

The interesting thing (to me) about defer is that it promotes making explicit what's implicit in languages like C++ with RAII. In C++ with RAII, handler would be closed up at the end of the lexical scope but it's implicit, Go makes you explicitly state that you intend for it to be closed using a defer. But the defer, stylistically not by requirement, being near the initialization is, arguably, clearer than the goto or the finally statement. It's also less error prone, you can scan the function and see that someone left out a defer close(handler). But if you use a cleanup block at the end (either with try/finally or with gotos) you have to jump back and forth between the top and bottom of the function in order to see that everything has been cleaned up and in the correct order.

Re: Goto (2007)

#140

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

Yeah to those still adamant about it, if you've ever used a: - function call - if sentence - any kind of loop You've used a GOTO under the hood. I hope you can live with yourself, you monster :)

The real hidden gotos are:

- state machines - early return of a function

Post reply on HN