Hacker News new | ask | show | jobs
by floatingsmoke 2848 days ago
A bunch of pioneer project like docker, kubernetes, etcd, prometheus etc. has been built with go and I don't believe that the maintainers suffered lack of generics and error handlers. On the other hand, as a new go programmer, I can really dive into their code base and understand each line of code without thinking twice. This comes from simplicity.

But these possible nested error handlers and generics will lead developers to think more than twice during writing or reading a code base. These ideas is not belong to go era but Java, C++ etc which go doesn't wanted to be like.

Someone here has mentioned that internal support of generics for slices, maps and other primitives. I think this can be the best solution for generics in go. For the error handling I think more elegant way could be found.

Please do not rush.

2 comments

> A bunch of pioneer project like docker, kubernetes, etcd, prometheus etc. has been built with go and I don't believe that the maintainers suffered lack of generics and error handlers.

Here's an experience report from k8s: https://medium.com/@arschles/go-experience-report-generics-i...

They've been using a code generator as a work around: https://github.com/google/gvisor/tree/master/tools/go_generi...

I think code generators are a better path than having the compiler generator the code for you. It's nice to look and see what is about to be built instead of waiting for it to be built then trying to debug with breakpoints.
It sounds like nested error handling will be the abnormal case and the "default" error handling will often be normal. That is, all you will notice in common use is that the boilerplate "if" is replaced with a "check".

It also sounds like the nesting does not escape the function itself. It always either returns error, or doesn't.

Well, How about multiple return values? How will "check" keyword handle return values other than error?

  func ReadFile(path string) (string, error) {
    b, err := ioutil.ReadFile(path)
    s := string(b[:])
    return s, err
  }
How this function would be written regarding to drafts?. I am really confused.
The draft doesn't include any actual examples with multiple returns on a function that uses check internally, but the behavior is fully specified:

> A check returns from the enclosing function by returning the result of invoking the handler chain with the error value.

> A return statement in a handler causes the enclosing function to return immediately with the given return values. A return without values is only allowed if the enclosing function has no results or uses named results. In the latter case, the function returns with the current values of those results.

> A handler chain function takes an argument of type error and has the same result signature as the function for which it is defined.

> If the enclosing function has result parameters, it is a compile-time error if the handler chain for any check is not guaranteed to execute a return statement.

So any handler defined in ReadFile would be required to return (string, error) or not include a return at all (such that other handlers got their chance to interact with the error).

check returns all non-error return values, so the function would be written as:

  func ReadFile(path string) (string, error) {
    b := check ioutil.ReadFile(path)
    return string(b), nil
  }
A handle block has access to all variables that are in scope. So if you have partial results that you want to return is a check fails you can return them in the handle block if they are already declared/assigned. The easier way to handle this is using named return variables which are always in scope. If no partial results have been assigned then they will be returned as zero values otherwise the partial results can be set.
What you're thinking of is the default handler (because if you wrote a handler, it would be your code returning things).

The default handler will populate the last element of the return list with the error, and return.

The other elements get zeroes, if not named, or they are unchanged from how they have already been set, if named.