• taladar
    link
    fedilink
    arrow-up
    3
    arrow-down
    1
    ·
    9 months ago

    On the other hand in Go you have to literally implement every other standard library function inline yourself because of its lacking expressiveness and it is total cancer to read, at least in the cases of the Go programs I looked at to debug some things which I wouldn’t really consider productive.

    • sugar_in_your_tea
      link
      fedilink
      arrow-up
      6
      arrow-down
      1
      ·
      9 months ago

      standard library

      The standard library is absolutely massive, so it has pretty much everything you’d need.

      expressiveness

      The syntax is so simple that there are rarely any surprises. Yeah, it’s not very expressive, but that’s a feature, not a bug, because I’ll pretty much never run into a clever bit of syntax that takes me time to understand. There’s a very strong idiomatic style, and quite often “one right way” to accomplish something.

      if err != nil { return err } will appear all over your code, but after reading and writing a lot of Go, it’s not distracting anymore.

      That said, I do have criticisms that lead me to avoid Go:

      • no protections against data races - no Mutex[T], map[T]U isn’t atomic, etc
      • interface{} being an container instead of a compiler hint - this means (*int)(nil) == nil but interface{}((*int)(nil)) != nil; that has caused so many bugs
      • no destructors - automatically closing files, mutexed, etc
      • can copy beyond the end of a slice, overwriting data in the backing array

      I could go on. My point is that there are far too many footguns for a language that’s designed to be simple. However, once you know what you look for, Go is really easy to read and write.

      That said, I don’t use Go anymore and write mostly in Python and Rust, Python for the prototypes/scripts, and Rust for anything I want to maintain longer term. I’m not looking for a middle ground anymore, but if I was, Go is really productive and easy to read, while having nice syntax for smaller microservices.

      • taladar
        link
        fedilink
        arrow-up
        4
        ·
        9 months ago

        What I had in mind when talking about that standard library thing was one case in particular that I had found where someone had to implement deduplication of elements in a vector/array/list (or whatever Go called it, don’t remember that bit) locally because Go does not support function generic over the type of container element.

        And the whole if err != nil { return err } bit is a huge part of what makes Go code unreadable. I have also found at least half a dozen bugs related to that construct where people just did not print any of the relevant information in error cases because of the lazy copy&paste of that construct in cases I had to debug.

        • sugar_in_your_tea
          link
          fedilink
          arrow-up
          1
          ·
          edit-2
          9 months ago

          deduplication

          The best solution here is a map, using keys as the set. So something like:

          func dedup(arr []T) (ret []T) {
              m := make(map[T]bool)
              for _, t := range T {
                  m[t] = true
              }
              
              // optimization: make ret the right size
              for t := range m {
                  ret = append(ret, t)
              }
          }
          

          I haven’t used Go’s new generics, but I’m guessing this would work fine, provided T is a value type (or can be converted to one). If you know you need deduplication at the start, just use a map at the start.

          If you don’t have value types, you’re going to have a hard time regardless of language (would probably need some OOP features, which adds a ton of complexity). But you can get pretty far with that pattern. If you add a Hash() int to your type:

          func dedup(are []T) (ret []T) {
              m := make(map[int]bool)
              for _, t := range arr {
                  h := t.Hash()
                  if !m[h] {
                      m[h] = true
                      ret = append(ret, t)
                  }
              }
          }
          

          err… people just did not print any of the relevant information in error cases

          That’s what error wrapping is for:

          if err != nil {
              return fmt.Errorf("context: %w", err)
          }
          

          This makes it so you can use the errors package to unwrap errors or check if an error is a given type. Or you can propagate it like I’ve shown above.

          So I see this as programmer error. Rust has the same issue since it’s easy to just throw a ? in there and bail early without additional context. The simpler form is easier to catch in Go vs Rust in a code review because it’s more verbose.

          It seems you don’t like verbosity, which is fair. I’m fine with verbosity, I don’t like surprises, and Go has enough of those that I generally avoid it.