Why is Rust being used to replace parts of the JavaScript web ecosystem like minification (Terser), transpilation (Babel), formatting (Prettier), bundling (webpack), linting (ESLint), and more?
Has Golang fizzled? It has struck me as too primitive, but basically on the right track.
My biggest issue with Golang by far is the close tie to Google. They are not our friendly innovator, time and time again they make decisions that will help them earn more ad money, and nothing else. And they have a lobg history of releasing something and then never fix the issues with it, and then more or less abandon it.
Other than that there are afaik some other issues with go, I’m not an expert but from what I hear the GC is quite aggressive and you can’t tell it to run when you want. Doing something time sensitive? Well, bad luck. GC time!
The GC in Go is fantastic IMO since it runs in a separate thread. I used it since 1.0 (switched our product from node.js), and dealt with all the the pain of an imprecise GC (fixed in 1.5?) and all the little improvements to arrive at it’s current state.
The main issues I have with it are pretty core to the language, unfortunately, such as:
interface{} is basically a void*, but since it’s a fat pointer, it can hold nil without itself being nil, which can happen by accident
runtime reflection is a bad habit, but it’s unfortunately really common
it’s really easy to deadlock by making stupid mistakes; if it had automatic unlocking based on scope (like Rust, or something like Python’s context managers), we could solve this, but defer just isn’t good enough
no destructors - with destructors, we could build a solution to deadlocks
Maybe they fixed some of those issues, idk, I haven’t used it for several years. I did use it for about 10 years though.
I assume you’re talking about runtime. AddCleanup()? That’s certainly nice, but it’s not the same as a destructor since it only runs at GC time. It’s useful for cleaning up data used by a shared library or something (e.g. something malloc’d by a C lib), but it only solves part of the problem.
I’m talking about scope guards. In Rust, here’s how you deal with mutexes:
{
letvalue = mutex.Lock();
... use value ...
// mutex.Unlock() automatically called
}
The closest thing in Go is defer():
mutex.Lock()
defer mutex.Unlock()
That works most of the time, but it doesn’t handle more complex use cases, like selectively unlocking a mutex early while still guaranteeing it eventually gets unlocked.
Rust fixes this with the Drop trait, so basically I can drop something early conditionally, but it’ll get dropped automatically when going out of scope. For example:
Without the last line, this prints c, b, a, i.e. stack order. With the last line, it instead prints b, c, a, because I drop b early.
This is incredibly useful when dealing with complex logic, especially with mutexes, because it allows you to cleanly and correctly handle edge cases. Things are dropped at block scope too, giving even more control of semantically releasing things like locks.
That said, 1.24 added WASM, which is really cool, so thanks for encouraging me to look at the release notes.
Thanks for taking the time to explain it. Indeed the new runtime method does not guarantee when the resource will be cleaned, so something like that Drop trait would be quite useful
True about Google ;). Yes, there are programs that really don’t want GC. I consider those to mostly be niche applications since most of us are fine with using e.g. Python, which has automatic storage management (won’t quibble about whether it is GC per se) that has occasional pauses. SImilarly, tons of important programs are written in Java, which is GC’d. Of course Java is tied up with Oracle just like Go is tied up with Google.
Go’s main problem from what I can tell is that the language itself is too old fashioned. I’ve used it but am not expert. It feels like an improved version of C, rather than a modern, type-safe language.
My biggest issue with Golang by far is the close tie to Google. They are not our friendly innovator, time and time again they make decisions that will help them earn more ad money, and nothing else. And they have a lobg history of releasing something and then never fix the issues with it, and then more or less abandon it.
Other than that there are afaik some other issues with go, I’m not an expert but from what I hear the GC is quite aggressive and you can’t tell it to run when you want. Doing something time sensitive? Well, bad luck. GC time!
Guess who’s one of the rounders of the Rust Foundation…
The GC in Go is fantastic IMO since it runs in a separate thread. I used it since 1.0 (switched our product from node.js), and dealt with all the the pain of an imprecise GC (fixed in 1.5?) and all the little improvements to arrive at it’s current state.
The main issues I have with it are pretty core to the language, unfortunately, such as:
interface{}
is basically avoid*
, but since it’s a fat pointer, it can holdnil
without itself beingnil
, which can happen by accidentdefer
just isn’t good enoughMaybe they fixed some of those issues, idk, I haven’t used it for several years. I did use it for about 10 years though.
Not sure if that’s what you are referring to as destructors, but they added a new way to have code run at resource collection in go 1.24
I assume you’re talking about
runtime. AddCleanup()
? That’s certainly nice, but it’s not the same as a destructor since it only runs at GC time. It’s useful for cleaning up data used by a shared library or something (e.g. something malloc’d by a C lib), but it only solves part of the problem.I’m talking about scope guards. In Rust, here’s how you deal with mutexes:
{ let value = mutex.Lock(); ... use value ... // mutex.Unlock() automatically called }
The closest thing in Go is
defer()
:mutex.Lock() defer mutex.Unlock()
That works most of the time, but it doesn’t handle more complex use cases, like selectively unlocking a mutex early while still guaranteeing it eventually gets unlocked.
Rust fixes this with the
Drop
trait, so basically I can drop something early conditionally, but it’ll get dropped automatically when going out of scope. For example:struct A(String); impl Drop for A { fn drop(&mut self) { println!("dropping {}", self.0) } } fn main() { let a = A("a".into()); let b = A("b".into()); let c = A("c".into()); drop(b); }
Without the last line, this prints c, b, a, i.e. stack order. With the last line, it instead prints b, c, a, because I drop b early.
This is incredibly useful when dealing with complex logic, especially with mutexes, because it allows you to cleanly and correctly handle edge cases. Things are dropped at block scope too, giving even more control of semantically releasing things like locks.
That said, 1.24 added WASM, which is really cool, so thanks for encouraging me to look at the release notes.
Thanks for taking the time to explain it. Indeed the new runtime method does not guarantee when the resource will be cleaned, so something like that Drop trait would be quite useful
True about Google ;). Yes, there are programs that really don’t want GC. I consider those to mostly be niche applications since most of us are fine with using e.g. Python, which has automatic storage management (won’t quibble about whether it is GC per se) that has occasional pauses. SImilarly, tons of important programs are written in Java, which is GC’d. Of course Java is tied up with Oracle just like Go is tied up with Google.
Go’s main problem from what I can tell is that the language itself is too old fashioned. I’ve used it but am not expert. It feels like an improved version of C, rather than a modern, type-safe language.