From C# to golang: less is more.
Tags: csharp, go, dotnet
Published: 2023-04-09 23:10:39

I had been writing .NET backend systems for more than 4 years. I've used it at work, side projects, and college projects.

Recently, I decided to use go to build the backend for a mobile app. I value simplicity, and therefore enjoyed go the moment I stepped through [go by example](https://gobyexample.com/)

Go prioritizes simplicity and intent. It is imperative and eliminates many complexities that haunt other languages by choosing a small subset of features. If you're coming from C# or java, you may feel confined by this minimal language at first. 
But you will quickly realize that go's strength comes from its simplicity.

## Things I like ##
### No need for external dependencies ###
Everything works out of the box. You can build an entire backend with middlewares, services, repositories, database connectors, etc. without ever looking beyond the standard libraries go provides.
In contrast, building a similar system in C# requires external dependencies.

### Go is not opinionated ###
There is no right way of doing things in go. For example, if you want dependency injection, you set it up yourself and pass your services as parameters. Go does not do any DI magic under the hood which makes you mindful of how you create and pass your objects.
Go offers more freedom than C# which can lead a new developer to stumble until they find what works best for them; but I see this as an advantage. It forces you to make deliberate choices about the structure and flow of your code rather than blindly conforming to a predefined structure others have placed for you.
.NET's minimal APIs are the closest you can get to Go in a C# backend. It's a step in the right direction.

### Built in testing ###
When testing in C#, I would usually use an external library like xUnit and another for mocking. In go, testing is built in. You don't need to rely on any 3rd parties to test. You can benchmark, setup before tests, and teardown after tests using the built in testing library.

### Less Boilerplate ###
It takes less code to get a server up and running than C# .NET or java spring. This is a hello world http server in Go:

```go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", HelloServer)
    http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
```
I know a minimal C# API is just as small if not smaller, but most backend C# systems use controllers. A typical C# codebase will likely be much larger and contain more boilerplate compared to go.

### Extremely Easy to Learn ###
You can learn go in a few days and build something with it in no time. The learning curve is far from steep. You just hop in and next thing you know, you're spinning up services in no time. This is also great for hiring: it's easy to get a junior dev on the team and they will be contributing soon after.

### Concurrency ###
Concurrency in C# is complex, especially for new developers. There are many ways to achieve concurrency but equally many caveats and catch22s. You can use Parallel.invoke, Parallel.forEach, Task.WhenAll, Task.WaitAll, and more. With these abstractions, you gain granular control over the task: you can control if it's blocking, configure cancellation, etc. But you also risk running into countless complications. Be careful if you don't want the bloodcurdling deadlock monster entrapping you. [example](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html)

In go, concurrency is straightforward and untroublesome. you simply use the keyword `go` to run a function asynchronously. For more control, you can use channels and waitgroups which go provides to allow for fine grained control. Go's goroutines are similar to C# tasks where multiple goroutines map to a small number of OS threads. One of the differences is goroutines contain normal go code while C# tasks need to be written (or potentially re-written) to be asynchronous.

## Things I Dislike ##
### Lack of Mature Generics ###
Generics were recently added to golang but they are not mature. You cannot use generics without instantiation which limits your options. Functionalities that are easily achievable with generics in other languages may require workarounds in go.
Another pertinent issue is the lack of support for union types on struct fields. See this [issue](https://github.com/golang/go/issues/48522).

### Lack of Built-in Higher-order functions ###
Go does not have higher-order functions out of the box. If you're used to using LINQ in C# or streams in Java - which use lazy iterators to filter and process data immutably - you might be disappointed. Technically, you could write functions like these in go using closures. In most cases, the performance and complexity overhead of these custom iterators outweigh the benefits. This is by design. Go is meant to be simple. But I still find myself missing the niceties of LINQ from time to time.

## Closing Thoughts ##
If you noticed, I did not compare performance because it doesn't really matter in this context. You can build very capable and performant systems with C#, go, java, rust, etc. Your performance bottleneck will almost never be one of these languages, but rather your design choices.
I also refrained from complaining about Go's error handling because, although it's not as good as Rust's or C#'s, it's still fine and forces the developer be mindful about handling the errors.

If you haven't already, give golang a try.

## None of this really matters; just build something ##
You can sit here all day arguing about syntax differences and language superiority, but it really doesn't matter. The best programming language is the one you're most productive with. So just go out there and build something. Build it with go, java, or even assembly. Just avoid idle hands.