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

>if err != nil { gets old even faster.

I don't understand this complaint about Go. Either a function can result in an error or it can't. If it can result in an error and you don't handle that error, your program's behavior is undefined. Undefined behavior is a bad thing.™

Go forces you to handle the error (or explicitly ignore it). That design choice results in remarkably stable programs.



I don't mind it, but I don't love it either. It's easy to write, but it would be nice if the compiler enforced error handling so that you did not need to run errcheck.

I completely fail at writing rust, but their implementation of error handling is much nicer.

Want to call a function, returning the error up to the caller on error? Instead of

  res, err := somefunc()
  if err != nil {
    return err
  }
You can just do

  let res = try!(somefunc());
If you need the function to succeed, similar to how go has some functions like MustCompile, rust has unwrap and expect:

  let res = somefunc().unwrap();
or

  let res = somefunc().expect("somefunc broke :(")
> Go forces you to handle the error (or explicitly ignore it).

But that isn't really forcing you to do anything. There are 3 ways you can deal with an error: handle, ignore, or panic. In many cases go programs ignore errors when they really should panic.

It is disappointing that the go "equivalent" that you often see in examples is

  res, _ := somefunc()
This is not handling an error or explicitly ignoring it, it is pretending that the function never fails.

rust makes it impossible to ignore the error, while also making it easy to skip error handling if you just want to abort on failure.

if I could write code in go + rust generics + macros and not have to deal with the lifetime borrow checking crap, I would be so happy :-)


> if I could write code in go + rust generics + macros and not have to deal with the lifetime borrow checking crap, I would be so happy :-)

You might like OCaml. It's easy to write like Go but with a much more useful type system. Downside: concurrency is not as pleasant as in Go… at all. Then again, I see a lot of things written in Go that aren't particularly concurrent and just happen to use Go because the author wanted a moderately high-level compiled language with automatic memory management. OCaml is excellent for those.


Also, Reason (a syntax on top of OCaml) makes OCaml look pleasant.


OCaml already looks nice <_<

(…Once you've used it for a bit. I admit it looks kinda weird at first.)

I recall Reason didn't yet support some important part of OCaml syntax, but I don't remember what that was.

Though I don't dispute that Reason is a good thing—Erlang saw a lot of growth from Elixir, hopefully Reason will get people into OCaml.


> OCaml already looks nice <_<

I agree; I find Reason to be a completely pointless project except if Facebook hopes to fracture the OCaml community.


Fortunately Reason compiles to ordinary OCaml compiled modules, so everything they write can seamlessly interop with regular OCaml in both ways.

But yeah, I also don't think the Reason syntax is that massive of an improvement: https://xivilization.net/~marek/blog/2016/05/19/reason-lets-...


In the same family, but with fairly nice concurrency (as far as I've gone with it) is F#. Even works on Linux, through mono now, and hopefully soon with .net core.


I thought try! and unwrap() just panic on failure? Isn't that exactly the same thing as `res, _ := somefunc()`?


My Rust on `try!` is rusty... no pun intended, iirc it is the equivalent of:

  if err := someFn(); err != nil { return err }
Where as `unwrap()` is:

  if err := someFn(); err != nil { panic(err) }
So a little dif than your example, fwiw. (Not agreeing or disagreeing with any of the statements, merely commenting on your question)


unwrap is also kind of neat in that it 'unwraps' the value, so something that converts strings to ints

  intval = convert("42").unwrap()
is like

  if intval, err := convert("42"); err != nil { panic(err) }
but you can not do

  intval = convert("42")
and pretend it can't fail, but in go you can do

  intval, _ := convert("42")
the rust unwrap would most similar to a

intval := mustConvert("42")

in go, but 'must' type functions don't exist for every method in every library.


unwrap panics on failure; try! is equivalent to

  match somefunc() {
    Ok(res) => res,
    Err(e) => return Err(e)
  }
in Rust; or

  res, err := somefunc()
  if err != nil {
    return err
  }
in Go.

Also, while I don't really know Go, doesn't

  res, _ := somefunc()
silently ignore the error instead of panicking?


> silently ignore the error instead of panicking?

Correct, with the minor note that it of course depends on your usage. If `res` is a pointer, any usage of it will panic because you ignored the err


Which means if you don't use that pointer for awhile you're going to have to do some backtracking to that ignored error


Which is why nobody writes that in production code; it's only used in examples and maybe tests


>In many cases go programs ignore errors

This would be an absolutely terrible Go programmer. Like "I only log in as root to avoid typing sudo and my password" level retarded.


Because exceptions can create much more readable and writable code, and DO THE EXACT SAME THING.

  func magic() (int, error) {
    a, err := foo()
    if err != nil {
      return 0, err 
    }
    b, err := bar()
    if err != nil {
      return 0, err 
    }
    c, err := baz()
    if err != nil { 
      return 0, err 
    }
    return a + b + c, nil
  }
is less readable, more pedantic, and just generally more soul crushing than:

  func magic() int throws SomeException {
    return foo() + bar() + baz()
  }
The explicit error check after every call actually doesn't provide anything useful. If any of foo(), bar(), or baz() fail, magic() is screwed, so why bother writing the same code three times? This shows that the proper place for the error handling isn't in magic(), it's in whoever is calling magic(), and yet, here' I am writing the same do nothing again, again and again in this function, and in all honesty, every function.

It's the multivalue return semantics that's the problem here. It's impossible to chain multivalue functions together to write anything succinctly. Worse yet, it's easy to simply ignore the error and create latent bugs because the first return value (which you're probably not ignoring) now has a "zero-value" which can be semantically valid. Whereas if I simply didn't catch the exception, it will be caught, perhaps by main(), but it will be caught, and I won't be populating my data structures with gibberish, which I can very easily do with Go.


  "It's impossible to chain multivalue functions together to write anything succinctly"
Lua's standard error return idiom is: nil/false, error-string, error-code. The Lua standard library also includes a function, assert, which will throw an error if the first argument is nil/false. The value thrown is the second return value, which in the idiomatic case is the error string. If the first argument is not nil/false, then assert returns the entire list of values.

Thus _you_ can choose the best approach based on _your_ needs. The following are _both_ examples of idiomatic Lua.

Example 1:

  local v, errdesc, errcode = foo()
  if not v then
    if errcode == 1 then
      ...
    elseif errcode == 2 then
      ...
    else
      error(errdesc)
    end
  end
Example 2:

  local v = assert(foo())
Example 2 (easy composition):

  local v = assert(foo(assert(bar())))
You're not limited to that. Neither assert nor error are specialized built-ins. You could throw a structured object with, optionally, a __tostring metamethod for string coercion; or you could write your own routine that manipulated the values differently. But in idiomatic Lua it's not common for thrown errors to be caught and differentiated, and code usually expects the thrown value to be a string or coercible to a string. If it's thrown it's usually because there's nothing particular that can be done, and so usually it's caught at the boundary of some logical transaction. But Lua allows you put that boundary anywhere, and you're not limited to that idiom. In some cases you want more structured exceptions, and you can have them. But as a module author the expectation is that you preserve the application's freedom of choice, so modules usually return the idiomatic tuple whether or not it's recoverable.

There are some errors that Lua will always throw, like memory allocation failures. These can be caught like any error, and the Lua VM is never put in an inconsistent state. But they're an example of the kind of non-specific error where you normally only catch them, if at all, at some logical transactional boundary--an image transformation in a photo editor, or a client request for a web server. Lua is, notably, one of the few scripting languages where allocation failure is recoverable. And rather trivially recoverable, in fact.

EDIT: error() is a specialized built-in: it's how you "throw" in Lua. But it will throw any value, not just a string.


One of the reasons why Go is so unwieldy is because it is designed to be simple. Its very easy to mistake simplicity for elegance; the reality is that simplicity can lead to elegance, but it doesn't have to.

Go is an interesting language when you have a project with dozens of developers, because it explicitly, by design, does not let you introduce any concept of "your needs". Go is not ashamed of the fact that it has one way of doing things, and that way is rarely elegant. But it _is_ effective.


I agree with that assessment. It's impossible to disagree because the designers have been very explicit about their motivations.

My argument was only that multivalue error returns are not necessarily so limiting. Rather, IME they're a great compromise that can permit the best of both worlds. I always wondered why Lua's idiom was never picked up by Go. So much of Go seems lifted wholesale from Lua, or at least a shared ancestor. Particularly Go's goroutines, lexical closures, and how cleanly they interoperate; both of those constructs are much more limited in languages other than Go or Lua because of shortcuts taken to simplify the [pre-existing, broken] implementations (e.g. JavaScript and Python). But especially Go's exception mechanism, with syntax and semantics likewise almost identical to Lua's.


With some effort, it can be made less horrible. I have a utility function that lets me write it like this, whenever I can get away with doing steps after an error has occurred:

  a, err1 := foo()
  b, err2 := bar()
  c, err3 := baz()
  return a+b+c, errs.Combine(err1,err2,err3)


Yeah that kinda works, except you have to make sure that you add your error code to the end every time you add something. Also, you're still stuck with doing the check if your code doesn't quit hit this pattern.


Yes yes and yes. Thank you for these perfect examples.


It's easier to use exceptions and pretend nothing ever goes wrong.


It's even easier to not use exceptions either and let your supervision tree, process monitor, hardware heartbeat switch and/or night operator handle it.


This is the right answer really. In large at least that's how sane operating systems deal with failed components (processes). They crash, but then they can restart if monitored by a supervisor (systemd / init). On a higher level most places handle it as well at machine / node level -- auto-failover, auto-provision, health-monitoring, centralized logging. All that is the right answer to building concurrent systems, except most current languages except a few (Erlang / Elixir) are stuck in the equivalent of early 90's Microsoft Windows 3.1 environment.


It's easier to use exceptions and not care about the origin of an error when the details are irrelevant.


Exactly -- I get annoyed by having to write 20 `if err != nil {`s when just one `catch` in another language would work. IMO, this is one instance of Go's maintainers conflating language simplicity with compiler simplicity.


> IMO, this is one instance of Go's maintainers conflating language simplicity with compiler simplicity.

Lol, I thought a long time about how I could express that feeling. now I understand, in Go the developer is doing the compiler's work ...


Perhaps the sentiment is valid, but practically speaking, in Rust you do even more of the compiler's work for it, no? I suppose one could just cherry-pick an analogue of the `try!` macro into Go.


Agreed, Rust's way of error handling with Result and try! (or ?) is far superior.


Monads are not that hard either :) Once you get used to Maybe/Option and Either, you don't want to look back to error codes or exceptions.


Easier to use exceptions and allow RAII to clean up resources automatically, making all error paths work smoothly.


For some value of "easier".


I would love it if there were a keyword to check if an error return value received was non-nil, and then return it and default values for other return types if so.

So:

     if x, err := foo(); err != nil {
       return 0, nil, err
     }
becomes:

     x := check foo()
with "check" being the keyword here.


Same. I've some spots in my code where I return three or four values (there's no logical reason to put them inside a struct...) and so I end up with a handful of

    if err != nil {
        return "", 0, err
    }
_all_ _over_ _the_ _place_. What I also don't like (perhaps I'm just being too uptight) is

    return myStructType{}, nil
The alternative is to use a named return parameter (which usually is good form), but it's still kinda gross, especially if it's a longer package + struct name.


You are not alone! I want things like this so much I've considered adding an m4/go-generate step into my projects to get them.


Nobody is suggesting to ignore errors. Checking every single return value in a block of code is a ton of boilerplate versus catching all the exceptions from that block in a single place.


Yeah but you aren't really capturing the difference fully.

If all of the exception handling code is at the end of the block, you lose context (which of the three file opens the this FileNotFoundException), scope (every variable you want to use during cleanup has to be declared outside the try, initialized to a sentinel, and then checked in the catch to be sure the try initialized it to the real value), locality (the code, the error handling, and possibly the finally block all deal with the same things, and should be in sync, but are spread out), and more...

Error codes are verbose, but exceptions have issues to.

Neither one is ideal for me but error codes are the lesser of two evils for the time being.


> you lose context

Sometimes you don't care about the context. You can have middle-ware code: [top level code]<->[your code]<->[library]. Maybe you don't care if library throws a "disk full" exception or "network down", littering that part with "if err != nil ... " might not be the best solution. Top level code might decide to log the error, retry, retry and log, send a page, or even just let another layer on top of it decide.

But as you say it is a trade-off. At lest Rust provides a neat try! macro, it goes a long way in handling boilerplate.

   let mut f = try!(File::open(filepath));
   let a_byte = try!(f.read_u8());
Perhaps Go would provide a source code transformation tool, to automatically expand code to do the equivalent.


Depends on the language for me. If I'm in Java and need to put try-finally blocks for cleanup of non-memory resources, then error codes are cleaner. If I'm in C++, with all objects using RAII to clean up after themselves, then exceptions are the simple way to go.


Even with raii and exceptions, I find myself disliking exceptions.

I like seeing which parts of my program can fail, and I want to be forced to consider what should happen in different failure scenarios.

I like explicit error handling because not only is it obvious where my program can fail (and why), but it is obvious where it can't fail too.

With exceptions, any line of code may fail. Or maybe not. It is all invisible.

(Now this assumes a language that enforces return code checking; I use C and a static analysis pass today, but a few languages get this built-in).


try-with-resources helps a lot, it basically invokes AutoCloseable.close for RAII-style cleanup.


Unfortunately, this isn't standard practice in Python. It's "catch the errors you know about" at very best.


Here's a paragraph from Rust's book on error handling [1] (written by BurntSushi who write quite a lot of Go too):

> You can think of error handling as using case analysis to determine whether a computation was successful or not. As you will see, the key to ergonomic error handling is reducing the amount of explicit case analysis the programmer has to do while keeping code composable.

Yes, handling errors is important and should be done, however it need not result in a block of text that people just skim over and that is easy to get wrong.

[1] https://doc.rust-lang.org/book/error-handling.html


I don't even notice the `if err != nil` anymore.

Is there a more elegant way to handle errors in a concurrent language than returning error values? In general, exceptions do not work well within async programming. Too many JavaScript developers use exceptions and I advise to return errors instead. At least Go is consistent.


> In general, exceptions do not work well within async programming.

Exceptions do work well with synchronous programming, which is Go's model.


Exceptions work fine in languages like Python with tornado gen coroutine. It depends whether the language as proper support for async functions.


Exceptions don't work fine in Python for single-threaded apps. Everything throws, and almost nothing catches. User provided malformed JSON? Hit 'em with an HTTP 500.


+1 for being consistent!


> Go forces you to handle the error (or explicitly ignore it)

Can you elaborate on this? This doesn't seem true. For example, see Hello World: fmt.Println returns an error, which is implicitly ignored.


> Go forces you to handle the error (or explicitly ignore it).

No, it doesn't. For example: https://play.golang.org/p/jGMmsaorez


Or even more generic. fmt.Print returns number of bytes written and an err if it failed for some reason. Those calls are rarely err checked.


Only in instances where you don't care about any of the other return values.


Monadic error handling deals with this in a vastly superior way. Essentially, automate away the boiler plate. Even Java-style exceptions are better.


Severe undefined behaviour by default doesn't appear to be a good language design choice.

Python is safe by default because you don't need to actively look for errors: when an error happens and there is no exception handling code you get a traceback, and the only way to ignore an exception is explicitly writing an (in)appropriate exception handling cause.


> Either a function can result in an error or it can't.

The problem is handling the error every time the function is called in the same place it is called and adding extra visual noise to the business logic.

Other ways to handle errors:

* Throw an exception. Program for the default happy path and throw an exception if something ... exceptional happened. This way code is not littered with 50% error handling which makes hard to read understand what it should be doing "normally". There is a trade-off there. But this has been implemented badly in other languages C++, Java and Go writers probably looked at that thought "No, way" and threw that idea away. I think Python does this right and it greatly goes to simplify code.

* Crash the goroutine (panic?). It is a bit like an exceptions but it applies to systems with concurrency units (goroutines, threads). However, in general that might not work as well because of two things -- shared memory, you can't just restart or assume a crashed goroutine hasn't scribbled over the heap memory, somehow or left data in an inconsistent state. And the other thing is goroutines don't have identifiers. so you can't say, I want to know if goroutine x crashes, then I want make sure y crashes as well, or I want to restart goroutine x and try again. But, that is not Go, that is Erlang / Elixir. Once you have seen process monitoring, linking, supervision trees and how they result in shorter, clearer code and less operational pain and suffering, it is hard to go back.

(Also if this sounds so alien and crazy, think about OS processes. A program has a pid. You can kill it using a pid, restart it. You know it hasn't scribbled over memory and messed up with other programs. This what sane operating systems do it has been this way for many decades, early Unix and then NT on windows. Other languages basically behave like Windows 3.1, which is cool, but is 1995 cool, not 2016 cool).

Should also mention Rust probably as well. Rust does a good job handling a lot proving there won't be data races at runtime between threads. Restarting a thread there is not as dangerous. But from what I guess still kind of awkward in the code. But even for the case explicit error handling, it has the try! macro which goes a good to visually simply the code. It does this because it decided that functions can return an agreed Result type which can be an ok value or an error. If it returns an ok, it passes the value along to the code that called, it if gets an error it returns early with the error. (Here it assumes both the caller and callee return Result. I don't know much about Rust so this might all be wrong, if anyone knows better please correct this ^).


I haven't write in Go, but I have written in Lua, which has a similar error handling convention.

I don't understand why "Go forces you to handle the error (or explicitly ignore it)". I find [a sample program](https://gobyexample.com/writing-files) and remove all the error checking code. Nothing unlike in Python happens. It compiles without warnings (even C can warn you about not using returned values).

I can see how Rust forces people to either handle the error, or explicitly ignore it. When you forget about it, it just doesn't compile.


>If it can result in an error and you don't handle that error, your program's behavior is undefined. Undefined behavior is a bad thing.™

There are way more advanced ways to handle errors than if err != nil boilerplate.


Such as?

In before: Unchecked exceptions that I can forget about instead of always handling the errors gracefully or consciously pushing down the stack.


>Unchecked exceptions that I can forget about

Not any more than you can forget about the returned error code in Go (val, _ := foo() etc). But with all the added convenience of exceptions.

What you'd seem to describe, and which Go lacks, are Optionals that you have to handle for every option.

And lots of other schemes (even Conditions).


>Not any more than you can forget about the returned error code in Go (val, _ := foo() etc).

I'm pretty sure that's an underscore right there, that very explicitly means "I'm consciously ignoring this error". Not sure what you're talking about with Optionals and Conditionals. Errors in Go are in the end structs that satisfy the error interface. That doesn't have to be an errors.Error, you can implement your own to satisfy more complex needs.


People love to complain about handling err, but how is it so much different from handing Maybe and Result in functional languages?


It is so much different because with monads you get the error handling automatically most of the time.

Unless you have very specific requirements for that one line, you don't go writing code like:

    case f x of
        Nothing -> Nothing
        Just y -> case f y of
            Nothing -> Nothing
            Just z -> z
You write:

    do
        y <- f x
        fy


your example went from readable to complete nonsense really quickly.

Mind rewriting

       do
        y <- f x
        fy
or annotating into something that makes sense?

say with an example like response, err := http.Get(someURL)

How would this example look like in haskell if error handling is abstracted away?


Let's say you're doing something with the result of http.Get, because otherwise it makes no sense. In Go, you'd have something like:

    resp, err := http.Get(someURL)
    if err != nil {
        return nil, err
    }
    res, err := DoStuff(resp)
    if err != nil {
        return nil, err
    }
    return res
In Haskelly-Go, you'd do:

    do
        resp <- http.Get(someURL)
        DoStuff(res)
The types of both http.Get and DoStuff would be monadic, and the result of the do block would also be monadic. When you eventually get to the place where the error is significant, you handle it there when "unwrapping" your value from the monad.


The Go code in your example could actually be written:

    resp, err := http.Get(someURL)
    if err != nil {
        return nil, err
    }
    return DoStuff(resp)


Yeah, but if you had 5 operations in Haskell, you'd write:

    do
        a <- operation1
        operation2 a -- This one returns nothing
        b <- operation3 a
        c <- operation4 b
        operation5 c
In Go, you'll keep repeating that 'if err != nil {' everywhere. And things get way more interesting with more complex monads, but error handling is simple.


Do-notation is sequenced evaluation, where the result of each statement is passed as the argument to the next (a simplification, but good enough). In this case there are two sequential operations and if either of them fails then it automatically propagates and results in the whole sequence returning a value that indicates failure. In Python, an (approximate) analog would be like this:

    from math import sqrt

    def f(a):
        if (a >= 0):
            return (True, sqrt(a))
        else:
            return (False, None)

    def do(a):
        (ok, y) = f(a)
        
        if ok:
            (ok2, y2) = f(y)

            if ok2:
                return (True, y2)
            else:
                return (False, None)            

        else:
            return (False, None)

    
    # example
    for x in [2, -2]:
        (ok, y) = do(x)

        if ok:
           print("Result: {}".format(y))
        else:
           print("Calculation failed.")

    # Output
    >>> Result: 1.189207115002721
    >>> Calculation failed.
     
This sort of thing is super awkward in Python, but is a common/natural pattern in Haskell (note: I realize this is not how you'd do this in Python, but I'm trying to make it equivalent to the Haskell). This does the exact same thing as the Python program above and is a more complete version of what the OP posted:

    import Control.Monad (mapM_)

    {- 
       Maybe Double represents a computation that either
       returns a Double or fails. In this case, fail
       when passed a negative number. Note: this is not
       the same as throwing an exception.
    -}

    f :: Double -> Maybe Double
    f x
      | x >= 0 = Just (sqrt x)
      | otherwise = Nothing

    -- Fleshed out version of OP's snippet
    eval :: Double -> Maybe Double
    eval x = do
        y <- f x
        f y

    {-
       do notation desugars to monadic binding; in this
       case eval can be desugared to the equivalent definition:
          eval x = f x >>= f
    -}


    -- Here's why this is convenient, because you can pattern match
    printIf :: Show a => Maybe a -> IO ()
    printIf (Just x) = putStrLn $ "Result: " ++ show x
    printIf Nothing = putStrLn "Calculation failed."

    -- This is basically the equivalent of the Python for loop
    main :: IO ()
    main = mapM_ (printIf . eval) [2, -2]
I know Haskell syntax is strange looking if you're unfamiliar with it, but it's mostly geared towards making certain patterns (like this one) convenient.


Or:

    f <$> f <$> x


Yes. Or even:

    f x >>= f
But I think those operators tend to confuse people that don't know the language, while do notation is very intuitive (although opaque).


Is the '<$>' operator called the tank operator? Because it looks like this to me: https://en.wikipedia.org/wiki/Mark_IV_tank


The operation is called "map". In Haskell it is the same as the "fmap" function, for historical reasons.

By the way, is this the Civilization III armor? I never knew what model they got the image from. It is British... I always expected it to be German for some reason.


Those languages have patterns that make Result easy to handle, without introducing exception handling. For instance, Rust has "try!", and is introducing the new "?" syntax, to turn an error Result into an early return, and make code with robust error handling feel like straight-line code.




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

Search: