I was coming with high hopes to the go language. It is not a bad language, but for application development there are far better options. Unless it is super efficient, I cannot understand why Google uses that purpose, you can be much more productive with C#. Maybe some day I will see the light, but for now I will consider the emperor is naked, given all the (non justified?) hype around the language.

The good

type Record struct {
  afield int
  anotherone string
}

func main() {
  r := Record{ 666, "999" }
  fmt.Printf("%v\n", r)   // Output: {666 999}
  fmt.Printf("%T\n", r)   // Output: pkg.Record
  fmt.Printf("%+v\n", r)  // Output: {afield:666 anotherone:999}
  fmt.Printf("%#v\n", r)  // Output: pkg.Record{afield:666, anotherone:"999"}
}
func new_p_int() (*int) {
  var a int = 666
  return &a // do not panic, the compiler's got your back
}

func main() {
  a := new_p_int()
  fmt.Printf("a=%p, *a=%v\n", a, *a)
}
type Nested struct {}
func (*Nested) shadow_method() int { return 999; }

type Record struct { Nested }
func (*Record) shadow_method(int) int { return 111; }

func main() {
  r := Record{}
  // This will NOT work since Record.shadow_method hid Nested.shadow_method even if they have a different signature
  //fmt.Printf("r.shadow(0)=%v, r.shadow()=%v\n", r.shadow_method(0), r.shadow_method())
}

Composition and polymorphism are separate concepts

In C++ you can achieve both composition and polymorphism with inheritance.

type If1 interface {
  method1() int
}
type If2 interface {
  method1() int
  method2() int
}

type Klass struct { /* defines method1, method2 */ }

func main() {
   var i2 If2 = &Klass{}
   var i1 If1 = i2 // works !
   // Even if Klass implements ALL interfaces, what counts is `i1` interface type.
   //var i3 If2 = i1
}
type INested interface {
  inner_method() int
}

type IRecord interface {
  INested
  a_method()
}

type Nested struct {
  innerfield int
}
func (*Nested) inner_method() int { return 999; }

type Record struct {
  Nested
  afield int
}
func (*Record) a_method() {}

func main() {
  r := Record{Nested{666}, 333}
  // Record "inherited" the methods from Nested so it does implement IRecord
  var ir IRecord = &r
  // No polymorphism : Record is NOT a Nested struct
  //var n Nested = r
  fmt.Printf("r.inner=%v, ir.inner()=%v\n", r.innerfield, ir.inner_method())
  // Output: r.inner=666, ir.inner()=999
}
type Klass1 struct {}
func (*Klass1) doit(int) int { return 111; }
 
type Klass2 struct {}
func (*Klass2) doit(int) int { return 222; }
 
func main() {
  r1 := Klass1{}
  r2 := Klass2{}
  var i1 interface{} = r1
  var i2 interface{} = r2
  
  r1 = i1.(Klass1)
  // there are no unsafe casts
  //r2 = i1.(Klass2)
  // to avoid panic use a type assertion
  r2, ok := i1.(Klass2)

  fmt.Printf("i1=%#v, i2=%#v, ok=%v\n", i1, i2, ok)
  // Output:
  // i1=main.Klass1{}, i2=main.Klass2{}, ok=false
}

A strong enforcement of convention over configuration

Paternalistic but it ensures uniformity across different projects.

# Example project structure
# /home/candide/go_workspace/
#     src/thismodule/
#         code.go
#         code_test.go
#     src/thatmodule
#         code.go
#         code_test.go
#         main/
#             thatmodule.go <-- package name must be "main"
#     pkg/<platform_arch>
#         thismodule.a
#         thatmodule.a
#     bin/
#         thatmodule        <-- exe are dropped at the root

export GOPATH:=/home/candide/go_workspace
MODULES:=thismodule thatmodule thatmodule/main

# Be careful it is a trap ! `go build` compiles but does NOT create any artifact
build:
	go install $(MODULES)
test:
	go test $(MODULES)
/* somemodule.go */
func Doit(a int) { ... }

/* somemodule_test.go */
func TestDoit(t *testing.T) { ... }
func ExampleDoit(t *testing.T) { 
  ...
  // Output: <expected stdout>
}
func BenchmarkDoit(b *testing.B) {
  for i := 0; i < b.N; i++ { ... }
}

The neutral

func producer(ctx context.Context) chan int {
  c := make(chan int)
  go func() {
    // do something fancy that takes time ...
    select {
    case c <- 666:
    // without this case the producer could deadlock if no one reads from the channel
    case <- ctx.Done():
    }
  }()
  return c
}

func main() {
  d := time.Now().Add(50 * time.Millisecond)
  ctx, _ := context.WithDeadline(context.Background(), d)
  c := producer(ctx)

  select {
  case i := <- c:
  fmt.Println("prod returned", i)
  case <- ctx.Done():
  fmt.Println("prod ignored", ctx.Err())
  }
}
type Klass struct { a int }
func (*Klass) doit() int { return 222; }

func main() {
  kp := Klass{666}
  var i1 interface{} = kp   // copy of kp
  var i2 interface{} = &kp  // reference to kp
  k1 := i1.(Klass)          // copy of i1
  k2 := i2.(*Klass)         // reference to i2,kp
  kp.a = 111
  k1.a = 222
  k2.a = 333

  // from the type of `i2` you cannot tell if this is a value copy or not
  i3 := i2
  fmt.Printf("i1=%v, i2=%v, k1=%v, k2=%v, kp=%v\n", i1, i2, k1, k2, kp)
  // Output:
  // i1={666}, i2=&{333}, k1={222}, k2=&{333}, kp={333}
}

goroutines vs async/await

I clearly do not have enough experience using goroutines but it bothers me that simple things are not as easy as using async/await

// Note sync.WaitGroup is useless for the goroutines, it is needed solely by their caller
func thisTakesForever (grp *sync.WaitGroup) { grp.Done() }
func betterGoGrabACoffee (grp *sync.WaitGroup) { grp.Done() }

func main() {
  grp := new(sync.WaitGroup)
  grp.Add(2)
  go thisTakesForever(grp)
  go betterGoGrabACoffee(grp)
  grp.Wait()
}
// Note sync void function => async Task function
private async Task thisTakesForever() { ... }
private async Task betterGoGrabACoffee() { ... }

public void SomeLogic() {
  var t1 = thisTakesForever();
  var t2 = betterGoGrabACoffee();
  Task.WaitAll(t1, t2);
}
func producerGenerator(d int, data []int) (chan int) {
  ch := make(chan int)
  go func() {
    for _,i := range(data) {
      time.Sleep(time.Duration(d) * time.Millisecond)
      ch <- i
    }
    close(ch)
  }();
  return ch
}

func linearConsumer(chs ...chan int) int {
  sum := 0
  for _,ch := range chs {
    for i := range ch { sum += i }
  }
  return sum
}

func consumerOnlyTwo(ch1, ch2 chan int) int {
  d,sum := 0,0
  open1,open2 := true,true

  for open1 || open2 {
    select {
      case d,open1 = <-ch1:
        if(open1) { sum += d }
        // Note that nil channels always block when attempting to read
        if(!open1) { ch1 = nil }
      case d,open2 = <-ch2:
        if(open2) { sum += d }
        if(!open2) { ch2 = nil }
    }
  }
  return sum
}

func consumerGoRoutines(chs ...chan int) int {
  sum := 0
  grp := new(sync.WaitGroup)
  grp.Add(len(chs))

  merger := func(ich chan int) {
    for d := range ich { sum += d }
    grp.Done()
  }

  for _,ch := range chs { go merger(ch) }
  grp.Wait()
  return sum
}

func consumerReflection(chs ...chan int) int {
  sum := 0
  cases := make([]reflect.SelectCase, len(chs))
  dones := make([]bool, len(chs))

  for i,ch := range chs {
    cases[i].Dir = reflect.SelectRecv
    cases[i].Chan = reflect.ValueOf(ch)
  }
  for !All(dones) { // All is NOT in the stdlib (MacGyver)
    idx, d, open := reflect.Select(cases)
    dones[idx] = !open
    if open { sum += int(d.Int()) }
    // If we do not nullify the channel, its corresponding case will fire non-stop
    if !open { cases[idx].Chan = reflect.ValueOf(nil) }
  }
  return sum
}

func main() {
  ch1 := producerGenerator(70, []int{1,2,3})
  ch2 := producerGenerator(50, []int{4,5,6})
  consumerOnlyTwo(ch1, ch2)
}
func someLogic() {
  ch := make(chan int)
  go goroutine(ch)
  <- ch
}

func goroutine(ch chan int) {
  panic("oops")
  ch <- 666
}

func main() {
  someLogic()
  // Output:
  // panic: oops
  // 
  // goroutine 4 [running]:
  // main.goroutine(0xc000018180)
  //         /usr/lib/go/src/my_module.go:49 +0x39
  // created by main.someLogic
  //         /usr/lib/go/src/my_module.go:44 +0x58
}
public static void SomeLogic() {
  var t = somethingDangerous();
  Task.WaitAll(t);
}

private static async Task somethingDangerous() {
  Task.Delay(66);
  throw new Exception("oops");
}

public static void Main() {
  SomeLogic();
  // Output:
  // [System.Exception: oops]
  //    at Program.<somethingDangerous>d__0.MoveNext() :line 15
  // 
  // [System.AggregateException: One or more errors occurred.]
  //    ...
  //    at Program.SomeLogic() :line 10
  //    at Program.Main() :line 25
}

Dependency inversion by implicit interface implementation

The common approach for dependency inversion is for the producer to provide a separate package (depending only on other interfaces) with the interface to its concrete implementation.

On the contrary, golang encourages private interfaces. Service consumers are free to define the piece of the service they need, and depend only on that part.

digraph {
  node [shape="box"]
  newrank=true;

  subgraph cluster_normal {
    label="Java/C#"
    style="dashed"
    client1 [label="service consumer2"]
    client2 [label="service consumer1"]
    backend [label="service implementation"]
    interface [label="interface"]
    client1 -> interface [label="depends_on"]
    client2 -> interface [label="depends_on"]
    backend -> interface [label="implements", contraint=false]
    { rank=sink; backend; }
  }
  subgraph cluster_golang {
    label="Golang"
    style="dashed"
    subgraph cluster_client3 {
      label="service consumer2"
      style="solid"
      interface3 [label="interface2"]
      interface5 [label="interface3"]
    }
    subgraph cluster_client4 {
      label="service consumer1"
      style="solid"
      interface4 [label="interface1"]
    }
    backend2 [label="service implementation"]
    backend2 -> interface3 [label="implements", contraint=false]
    backend2 -> interface4 [label="implements", contraint=false]
    backend2 -> interface5 [label="implements", contraint=false]
    { rank=min; interface3; interface4; interface5; }
    { rank=sink; backend2; }
  }
  { rank=same; client1; interface3; }
  { rank=same; backend; backend2; }
}

This is a very new idea for me. I like it because :

type Socket struct { /* socket methods ...*/ }
type BlockDevice struct { /* block device methods ...*/ }

// on a separate source repo
type IFileLike interface {
  Open(string) int
  Read([]byte, int) int
}

func client_logic(if IFileLike) { /* do stuff */ }

func main() {
  client_logic(&Socket{});
}
public class Socket { /* socket methods ...*/ }
public class BlockDevice { /* block device methods ...*/ }

// on a separate source repo
public interface IFileLike {
  public int open(string path);
  public int read(byte[] buf, int len);
}

private void client_logic(IFileLike if) { /* do stuff */ }

public static void Main() {
  // You are screwed since the authors of Socket and BlockDevice did not implement any common interface from the beginning.
  // client_logic(new Socket());
}

But in real life, I am afraid this will happen :

/*** in module1.go ***/
type PersitentStore struct {}
func (*PersitentStore) ReadValue(key string) []byte { ... }
func (*PersitentStore) ListKeys(pat string) []string { ... }
func (*PersitentStore) MultiReadValue(keys []string) [][]byte { ... }

/*** in module2.go ***/
type IFancyReader interface {
  ListKeys(pat string) []string
  MultiReadValue(keys []string) [][]byte
}
func ReadAllKeysMatching(r IFancyReader, pat string) { ... }

/*** in module3.go ***/
type ISimpleReader interface {
  Read(key string) []byte
}

// works pretty well with ISimpleReader since it does not use the persistent store advanced capabilities.
func DoSomethingPrettySimple(r ISimpleReader) { ... }

func DoSomethingSpectacular(r ISimpleReader) {
  // At this point you realize ISimpleReader will not cut it. You need to duplicate IFancyReader in this module too.
  // __ FACE PALM __
  //keys := ReadAllKeysMatching(r, ".*")
}

These short comings, in my opinion, make this mechanism unsuitable for large corporate code bases where employees are rewarded for fast delivery, not quality.

The bad

func mutator(m map[int]int, sl []int, ch chan int, err error) {
  m[666] = 666
  err = fmt.Errorf("mutated")
  sl[0] = 666
  ch <- 666
}

func main() {
  var m map[int]int = nil
  var sl []int = nil
  var ch chan int = nil
  var err error = nil

  m = make(map[int]int)
  sl = []int{333,444}
  ch = make(chan int, 2)
  // error type is a snowflake, it can be assigned nil but has value semantics
  err = fmt.Errorf("not_mutated")

  mutator(m,sl,ch,err)
}
func mutator(b1 []int) {
  b1[3] = 222
}

func main() {
  a1 := [5]int{0,0,0,0,0}
  a2 :=  []int{0,0,0,0,0}
  a3 := a1    // value copy !
  a4 := a2    // reference copy !
  a3[0] = 666
  a4[1] = 333

  var a5 []int
  // This hopefully does not compile, you cannot convert arrays to slices silently.
  //mutator(a1)
  //a5 = a1
  a5 = a1[:]
  a5[2] = 111
  fmt.Printf("a1=%v, a2=%v, a3=%v, a4=%v, a5=%v\n", a1, a2, a3, a4, a5)
}
type Record struct {
  afield int
}

func (r Record) mutate_fiasco() {
  r.afield = 666
}

func (r *Record) mutate_ok() {
  r.afield = 666
}

func main() {
  r := Record{}
  r.mutate_fiasco()
  fmt.Printf("r=%v\n", r) // Output: r={0}
}
something := producer() // ok
something  = producer() // complains `something` has not been declared
// The rule is new(T) is equivalent to the statements
var t T
p_t := &t 
// The problem is that each type nil value behaves differently.

func main() {
  a := new(int)
  b := new(map[int]int)
  c := make(map[int]int)
  d := new(chan int)
  e := new([]int)
  f := make([]int, 3)

  // b contains a "nil map" which will paninc if used
  //(*b)[0] = 0
  c[0] = 0

  // Writing and reading to a nil channel does NOT panic but blocks forever ...
  go func() { (*d) <- 666 }()
  //<- (*d)

  f[0] = 666
  // the nil slice has a zero length so it is kind of useless
  //e[0] = 666

  fmt.Printf("*a=%v, *b=%v, c=%v\n", *a, *b, c)
  // Output: 0, map[], map[0:0]
}
type Nested struct {
  innerfield int
}

type Record struct {
  *Nested
  afield int
}

func main() {
  r := Record{}
  // This will panic since the embedded pointer is nil
  //fmt.Printf("r.inner=%v\n", r.innerfield)
}
type If interface {
  doit() int
}

type KlassPointer struct {}
func (*KlassPointer) doit() int { return 111; }

type KlassValue struct {}
func (KlassValue) doit() int { return 222; }

func main() {
  var ip1 If = &KlassPointer{}
  var iv1 If = KlassValue{}

  // KlassPointer* implements If NOT KlassPointer
  //var ip2 If = KlassPointer{}

  var iv2 If = &KlassValue{}
  // Be careful it is a trap : panic at runtime, the compiler will accept a cast *KlassValue -> KlassValue
  //kv := iv2.(KlassValue)

  // This is OK both KlassPointer* AND KlassPointer implement the empty interface
  var ii interface{} = KlassPointer{}
  // However, cannot call **pointer method on temp object**
  //ii.(KlassPointer).doit() // cannot call pointer method on temp object
}

Dude, where are my batteries ?

int[] numbers = {1,2,3,4,5,6,7,8};
var r = numbers.Where(i => i % 2 == 0)
               .Select(i => i*i)
               .Aggregate((agg, i) => agg + i);
func setupClass() { ... }
func setup() { ... }

func unitTest1(t *testing.T) { ... }
func unitTest2(t *testing.T) { ... }

func parametricUnitTest(t *testing.T) {
  var data = []struct {
    name string
    param int
  } { ... }
  for _,d := range data {
    t.Run(d.name, func(t *testing.T) {
      // do something with `d.param`
    })
  }
}

func TestSuite(t *testing.T) {
  setup()
  t.Run("unitTest1", unitTest1)
  t.Run("unitTest2", unitTest2)
  t.Run("unitTest3", parametricUnitTest)
}

func TestMain(m *testing.M) {
  setupClass()
  os.Exit(m.Run())
}
#define DIE_IF_NEGATIVE(result, func, ...) \
  result = func(__VA_ARGS__); \
  if (result < 0) { \
    perror(#func "@" STR(__LINE__)); \
    exit(1); \
  }

int main() {
  int fd;
  DIE_IF_NEGATIVE(fd, open, "/middle/of/nowhere", O_RDONLY);
  // continue doing awesome stuff ...
  return 0;
}

You could do something similar using panic if only golang had macros.

The ugly

/* this will NOT compile */
if something_good {
} 
else { // <-- breaks !
}
func this_actually_compiles() (brain_fck int) {
  brain_fck = 666
  return
}

// You could argue that named return vars, reduce boiler plate when returning errors (or their abscence).
// I think error passing is so broken this will not do any more than confuse readers.

func implicit_return() (answer interface{}, err error) {
  stuff, err := do_something_risky()
  if err != nil {
    return // nil, err
  }
  return magic(stuff) //, nil
}

Please repeat yourself : if err := nil

func trySomething() error {
  if err := operation1(); err != nil { return os.ErrPermission }
  if err := operation2(); err != nil { return os.ErrInvalid }
}

func someLogic() error {
  if err := trySomething(); err != nil {
    if errors.Is(err, os.ErrPermission) {
      // recover and continue
    }
    return err // nope, can't handle this one
  }
}

func main() {
  if err := someLogic(); err != nil {
    fmt.Printf("this is a definite fiasco: %v", err)
  }
}
type mybase struct { s string }
func (e mybase) Error() string { return e.s }

type mywarn struct { mybase }
type myfail struct { mybase }

func trySomething() error {
  if err := operation1(); err != nil { return &mywarn{"acceptable but not good"} }
  if err := operation2(); err != nil { return &myfail{"we are screwed"} }
}

func someLogic() error {
  err := trySomething()
  switch err.(type) {
    case nil:
    case mywarn: // recover and continue
    default: return err // nope, can't handle this one
  }
}

func main() {
  if err := someLogic(); err != nil {
    fmt.Printf("this is a definite fiasco: %v", err)
  }
}
func top_stack() error {
  return fmt.Errorf("oops\n%s", debug.Stack())
}

func middle_stack() error {
  if err := top_stack(); err != nil {
    return err
  }
}

func main() {
  if err := middle_stack(); err != nil {
    fmt.Printf("this was a fiasco: %v", err)
  }
}

defer/recover : terrible solution for RAII, error recovery

func CopyFile(dstName, srcName string) error {
    src, err := os.Open(srcName)
    if err != nil {
        return err
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return err
    }
    defer dst.Close()

    io.Copy(dst, src)
}
public void CopyFile(string dstName, string srcName) {
    using (var src = File.Open(srcName))
    using (var dst = File.Create(dstName)) {
      src.CopyTo(dst);
    }
}
func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println("Detected panic from dodgy_code.")
    } else {
      fmt.Println("Returned normally from dodgy_code.")
    }
  }()
  dodgy_code(nil)
}

func dodgy_code(arg interface{}) {
  fmt.Println("dodgy_code got arg", arg)
  panic(arg)
  // Output:
  // dodgy_code got arg <nil>
  // Returned normally from dodgy_code.
}
func main() {
  v := 1
  {
    v := "chocolat"
    fmt.Println("Hello, block", v)
    // defer is **hoisted** to the function block !!
    defer func() { fmt.Println("bye, block", v) }()
  }
  fmt.Println("Hello, playground", v)
  // Output:
  // Hello, block 1
  // Hello, playground 12
  // bye, block 1
}
func a_trap(in int) (out int) {
  out += 333
  defer func() { out++ }()
  return in
}

func main() {
  fmt.Println("be careful", a_trap(666))
  // Output:
  // be careful 667
}

func main() {
  v := "first"
  defer fmt.Println("unwrapped", v)
  defer func() { fmt.Println("wrapped", v) }()
  defer func(w string) { fmt.Println("explicit", w) }(v)
  v = "second"
  // Output:
  // explicit first
  // wrapped second
  // unwrapped first
}

Features I need to try

Conclusion : maybe it is time to try rust ?