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

For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language.

The problem of 'summing any kind of list' is not a problem that is solved in Go via the proposed kind of parametric polymorphism. Instead, one might define a type, `type Adder Interface{Add(Adder)Adder}`, and then a function to add anything you want is fairly trivial, `func Sum(a ...Adder) Adder`, put anything you want in it, then assert the type of what comes out.

When it comes to iteration, there is the generator pattern, in which a channel is returned, and then the thread 'drinks' the channel until it is dry, for example `func (m myType) Walk() chan->myType` can be iterated over via `range v := mt.Walk(){ [...] }`. Non-channel based patterns also exist, tokenisers usually have a Next() which can be used to step into the next token, etc.

The Nil pointer is not unsafe as far as I know, from the FAQ: http://golang.org/doc/faq#no_pointer_arithmetic

The writer seems to believe that functions on nil pointers crash the program, this is not the case. It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing.

Go is not flawless by any means, but it warrants a specific style of simplistic but powerful programming that I personally enjoy.



>The writer seems to believe that functions on nil pointers crash the program, this is not the case. It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing.

And what happens when you don't check? It crashes. That's the unsafe part.

These crashes are simply not possible in Rust and Haskell, and the type system notifies you if failure is possible (because the function will return an Option/Maybe).


Woa, Rust is great, but I think you're going a little far on the hyperbole there.

You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code; that might only restrict errors of that nature to code that uses unsafe blocks.

Practically speaking that's pretty common; once you enter an FFI unsafe block, you lose all type safety; but you can totally do it without FFI too. Eg. using transmute().

In fact, there's no way to know if you code contains 'hidden' unsafe blocks wrapped in a safe api in some 3rd party library that might cause a mysterious segfault later on.

You can argue that 'if you break the type system you can do anything, obviously'; that's totally true.

I'm just pointing out the statement: "These crashes are simply not possible in Rust and Haskell" <-- Is categorically false.

You can chop your own arms off in Rust just like anything else (including Go).


> You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code; that might only restrict errors of that nature to code that uses unsafe blocks, but practically speaking that's pretty common; once you enter an FFI unsafe block, you lose all type safety; but you can totally do it without FFI too. Eg. using transmute().

Not directly addressing what you're saying, but, IME people are far too quick to use `unsafe` code. One needs to be quite careful about it as there's a pile of invariants that need to be upheld: http://doc.rust-lang.org/master/rust.html#behavior-considere...

> once you enter an FFI unsafe block, you lose all type safety

You don't lose all type safety, especially not if the FFI bindings you're using are written idiomatically (using the mut/const raw pointers correctly; wrapper structs for each C type, rather than just using *c_void, etc).


Maybe "unsafe" is being used to mean different things, here. Some may interpret it as to refer to unsafe memory access. Others may use it to mean possibility of crashing at run time (due to null pointer dereference).


Null pointer dereference is unsafe memory access.


Not if you just throw something like an exception when the program tries to do it instead of actually accessing that memory location.


that's what mapping an -rwx page at 0x0 does, and as a result it segfaults, which is an access violation.


There is some subtlety about dereferencing a null pointer. Many languages (C, C++, Rust) state that *NULL is undefined behaviour, that is, the compiler can assume that it never happens and optimises based on this. This can lead to a "misoptimised" program that doesn't actually segfault when the source suggests it should.

http://blog.llvm.org/2011/05/what-every-c-programmer-should-...


No, you are wrong.

First, mapping that page doesn't cause all null pointer dereferences to segfault.

And second, the language doesn't require a segfault. In fact, it explicitly permits the implementation to do whatever it likes.

That is the difference between safe and unsafe. It is in the language definition.


"the language"... which one? AIUI, Go doesn't state that null dereferences are undefined behaviour, but rather that they are guaranteed to panic.


"The language" ?

C doesn't define behaviour of a null deref, but most compilers map a -rwx page there to ensure that attempts to deref fault.

In what circumstance do they not?


The compilers don't map pages there, the operating system does. The problem is the compiler will optimise assuming that a null deref never happens, so you can have source that looks like it should crash due to a null deref, but the compiler has "misoptimised" it to have very different behaviour.

http://blog.llvm.org/2011/05/what-every-c-programmer-should-...


...I'm talking about throwing an exception or something similar, not getting a segfault. Like in Java.


Programs that crash with null pointer dereferences are not very useful.

Considering that "safe" removes most of the usefulness of the word "safe".


Specifically, the word safe, when referring to type systems means "memory safe". Meaning the compiler or runtime either prevents bad memory accesses by construction or ensures dynamic checks are in place that throw an exception or halt execution in case a bad memory access was about to occur. It means the program isn't accessing uninitialized memory and isn't vulnerable to buffer overflows etc. It doesn't mean your program won't ever crash.

I agree it's better to avoid null dereference errors by not putting null in your language, but by the normal meaning of safe here, go is safe.


It does not mean "memory safe" in general. It means different things in different contexts.

Also, I wasn't making a point about the word "safe". I was making a point that if go is "safe", then the word "safe" is useless.

There is a problem here. Don't want to call it "unsafe"? Ok. Call it crashy, instead.


> It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing.

I disagree: if the construction can fail, the constructor must return an error, which will be checked; only if the error is nil can the process continue. There shouldn't be logic on the actual data returned to assert whether a constructor worked or not.


I meant something like this:

http://play.golang.org/p/eqnDLVMHGA (pseudocode)


>For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language.

If that's so, it's because it's a language that also fights lots of things a modern programmers wants to do/have.


>When it comes to iteration, there is the generator pattern, in which a channel is returned, and then the thread 'drinks' the channel until it is dry, for example `func (m myType) Walk() chan->myType` can be iterated over via `range v := mt.Walk(){ [...] }`. Non-channel based patterns also exist, tokenisers usually have a Next() which can be used to step into the next token, etc.

Actually using channels as a general iterator just for the sake of using the range operator is considered as an anti-pattern. The reason is not performance (although it has a cost), but the risk of leaking producer goroutines. Your example:

    for v := range mt.Walk() {
      if blah {
        break
      }
    }
How will the goroutine writing into the channel returned by mt.Walk know when there are no more consumers which will possibly read from it?

One way out is:

    done := make(chan struct{})
    for v := range mt.Walk(done) {
      if blah {
        break
      }
    }
    close(done) // or defer close(done)
Picking the right cleanup is error-prone.

What about errors? How will mt.Walk tell you that it had to interrupt the iteration because an error happened? Either your channel has a struct field containing your error and your actual value (unfortunately Go lacks tuples or multivalue channels).

Furthermore uncaught panics in the producer goroutine will generate a deadlock, which will be caught by the runtime, but it will halt your process. One way to do it is:

    errChan := make(chan error)
    for v := range mt.Walk(errChan) {
      if blah {
        break
      }
    }
    err := <-errChan
The producer will use the select statement to write both to errChan and your result channel. The success of writing to errChan is a signal for the producer that the consumer exited. However same thing here about relying on the last statement being executed to avoid a leak in case of returns or panics. Here the defer is less nice since you're supposed to do something with the error:

    func Example() (err error) {
      errChan := make(chan error)
      for v := range mt.Walk(errChan) {
        if blah {
          break
        }
      }
      defer func() {
        err = <-errChan
      }()
    }
Next-style methods just pass through the panics, and allow you to handle errors either by having a func Next() (error, value) or with this pattern which moves the pesky error handling outside:

    i := NewIterator()
    for i.Next() {
      item := i.Item()
      ...
    }
    err := i.Error()
First, any panic that happens inside either your code or the generator will bubble through. Second, if you return from your loop body, you will have to provide your own error (the compiler will remind you about your function signature, if in doubt). You can return early if the iterator can be stopped and GCed out (i.e. it doesn't handle goroutines or external resources), otherwise you'd have to call a cleanup as with channels.

The rule of thumb with Go should be that you don't have to do things just because they use some syntactic sugar. After a while you start to think about beauty in terms of properties not about calligraphy.

However, I do see this as a weak point of the language, which hopefully can be solved by education; after all Go is so simple to learn that you might be tempted to make it look even simpler. But the fact that the language has (almost) no magic, it means that you can actually understand what some code does, which imho outweighs the occasional syntactical heaviness or having to learn a few patterns.


I'm not suggesting these are the best patterns, or even 'good' patterns. I am simply drawing a parallel between them and pythonic iterators. When it comes to errors from generator patterns, I've gone between your suggestion and chan<-struct{T;error} when channel based generator patterns are appropriate.


> put anything you want in it, then assert the type of what comes out

... which is exactly what the article mentions and criticizes?




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

Search: