Recently I decided to learn Go and my first impressions were quite good. Go is a very nice language and easy to get started with. The go routines is probably the best feature of the language and I really wish other languages followed this model. The defer
feature is also very nice.
However, as I continued learning, some things started feeling odd, some design decisions that looked strange. For example, the only loop construct is for
, and there is no way to create a loop that iterates over a sequence of numbers, like for i in range(10)
in Python or for i in 0..10
in Rust, you have to use a for loop as in C: for i := 0; i <= 10; i++
.
After a while I realized: Go tries so hard to be like C that it does not even try make improvements that make it look too different than C.
Now let's compare the RWMutex
from Go with RWLock
from Rust:
lock := sync.RWMutex{}
lock.RLock()
// do someting
lock.Unlock()
This code will compile just fine, but will fail at runtime, because I called lock.Unlock()
instead of lock.RUnlock()
. You might say that is the developer's fault for no paying attention and calling the wrong method, and you are right, but let's take a look at the equivalent in Rust:
let lock = RwLock::new(1);
{
let read = lock.read().unwrap();
// do something
}
First thing we notice is that the RWLock
operates with a value, since is very common the use a lock to synchronize access to something. Then we open curly braces to start a scope because lock.read()
returns a guard that releases the lock when it goes out of scope, so there is no wrong method to be called. The error handling may look annoying but it is there for a reason.
For me this design decision for the Go RWMutex
seems very strange, it could be much better if RLock
returned a handle in which you would call Unlock
and I cannot think of any reason for this other than to look more like C.
But Rust is not perfect, though, function signatures can become messy, specially with async, and dealing with lifetimes is not fun. And compilation time...
I will continue learning Go, it is really a nice language, but I still prefer Rust, I feel much safer with it (pun intended).
Top comments (12)
The goal is not to "be like C". The developers are keeping the language clean, by preventing the addition of redundant features. You don't need a
for i in range(10)
if you can already do the same with a normal for-loop. It's less flexible, and the code doesn't become shorter, and hardly adds readability.There is a
for k,v := range things
for looping over maps/slices/arrays though, and one could argue whether this is an unnecessary feature, but I think this construct is much more common and practical in real world applications, than looping over a range of fixed integers. Also, it provides the key and value of the array/slice/map at once, making the code much more readable. So I think this adds a lot of value, whereas thefor i in range(10)
doesn't.The sad part is that they plan on adding
range(10)
andrange(iterator)
in the next versions. This is the reason why they pulled back themaps.Keys
andmaps.Values
functions from1.21
. I am really hoping that the language does not step too far from its cleanliness ideas.Indeed, the language I find the most problems with is Rust trying to be C++ish. This is because Rust is a functional language disguising itself in imperative syntax. This leads to false assumptions about execute behavior and order. Rust would be much better served in something like Haskell style declarative syntax, or maybe learning from ocaml syntax.
The other problem I find in Rust is it has many quirky and disjunctive notationals that feel like old-school perl, slapped on features that feel like php, and multiple disjunctive syntax forms (decorators, macros, templates, and more), all of which may be required even in rather trivial applications. All of this easily leads to logic and design errors that are then very hard to find after. Compiling and executing "correctly" but still wrong and much harder to then fix is not better but simply a different tradeoff.
You prefer a whole language because of some looping syntax sugar and a read/write lock API?
Btw an implementation of an RWMutex returning a handle which calls the correct unlock method would be like 20 lines of code.
There is a proposal for adding something similar to Python's
for i in range(10)
in Go, but I really don't think it is necessary.It is a spec change that will add no actual value to the language.
About the Mutex, it has many other points of failures besides the one you mentioned...
For real I've never had problems with it in production, because it is easy to notice those mistakes at development, and also because it is discouraged for most scenarios where you can use channels.
Go came many years before Rust and it was not developed with compile time safety and correctness in mind.
It is a trade-off... The language is simpler, there are no lifetime annotations, borrowing and moving, but there are less compile time guarantees too.
For me those are very different languages with very different use cases.
Yes, Rust can be used for regular backend development, but it shines when used for critical security systems like databases, operating systems, browsers, and so on.
Yes, Go can be used for embedded systems, but it shines more for cloud, infrastructure, and network applications.
A propósito tenho uns artigos sobre Go em português se te interessar :)
The main philosophy of GO is to keep the language clean and not introducing redundant features. Things that can be done with one way, why need separate dedicated feature to do the same.. as @gwijnja mentioned you can use
for i, v := range items
to loop over. Also calling a wrong method which fails at runtime is also not big of a deal, it can be easily caught during tests.Rust and Go both have their pros and cons but deciding which language is better by just comparing if one has sugar coated features or not is not justifying.
As many have stated before the aim of Go differs from Rust with simplicity at it's core.
When I first started using Go, I will admit.. I did not like how rigid it felt by limiting to only one loop operator etc. However after using it for enterprise apps and building micro-services with it, I came to appreciated the rigid simplicity.
Good point about RWLock. Your idea that Go is trying to be like C makes no sense. C has for, while, and do-while - Go just has for to be minimalist. Also C uses handles (eg FILE *) - Go 's philosophy is to make the zero (uninitialised) value ready-to-go. If you want a handle just take the address.
BTW I learnt Rust first and loved it but I am now using Go. My first impressions of Go were not good but since using it full time I have been able to stop talking my blood pressure medication (serious).
Interesting. I have been wanting to pick up Go for a while but now but still haven't had the chance. Are there any other examples of annoying or questionable (perhaps just unfamiliar) design decisions you've come across so far?
Comparing apples and oranges publicly is just self-hurting rubbish. I use both languages equally and I am happy with it, because they serve different purposes. I use also Ada and other stuff when it is needed for mission-critical places.
If you are still switching from one to another just because of rmutex syntax, then you are still not in the level of engineer, but a fanboy searching for a comfort zone.
Meh.
Go is my second language(Java is the first) and I'm preparing to learn some Rust next. Like your article!
You didn't understand language at all, like not even close