While most people highlight the difficulty of picking up the syntax, I find Rust to be an incredibly tedious language overall. Zig has a less expressive type system, but it compiles much faster (though not as fast as Go). I like what Zig and Odin folks are doing over there.
I like the balance Go strikes between developer productivity and power, though I dearly miss union types in Go.
Don't make the fallacy of conflating Rust's slow compile time with its "advanced" (not really, it's 80's tech) type system. Rust compilation is slow for unrelated reasons.
Personally, I write java at my day job and the type system there makes me loooong for rust.
But, it's just a tool, and the tools I choose reflect the type of stuff I want to build. The JVM is extremely impressive in its own right. You're just not going to to find any one runtime or ecosystem that hits every niche. I'm happy to leave the language favoritism to the junior devs—for the vast majority of situations, what you're building dictates which language makes the most sense, not vice versa.
I've been involved in a few successful large scale projects and never felt like the type system of Go is holding me back too much. Sure the error handling could be better, union type would make interface munging easier, but the current state after generics isn't too bad.
Last time I checked, C# had clean and focused syntax for working with collection types. Could you provide an example?
It's just that compile times and DevEx haven't been a priority for most projects.
But sure, LLVM and interfacing with it is quite possibly a big contributor to it.
However, it is actually a good example regarding tooling, as the Haskell ecosystem has interpreters and REPL environments available, for quick development and prototyping, something that is yet to be common among Rustaceans.
Ideally we would be having the F# REPL/JIT, plus Native AOT for deployment, as comparable development workflow experience.
Naturally F# was chosen as example, because that's your area. :)
Not being negative per se, I also would like to have something like Haskell GHCi, or OCaml bytecode compiler, as options on rustup, so naturally something like this might eventually come.
That can be addressed by passing the slice as a pointer: https://go.dev/play/p/h9Cg8qL9kNL
Slices are passed partly by value (the length), partly by reference (the data).
func takeSlice(s []int) {
slices.Sort(s)
}
From your explanation, you would expect that to not mutate the slice passed in, but it does.This can have other quite confusing gotchas, like:
func f(s []int) {
_ = append(s, 1)
}
func main() {
s := []int{1, 2, 3}
f(s[0:2])
fmt.Printf("%v\n", s)
}
I'm sure the output makes perfect intuitive sense https://go.dev/play/p/79gOzSStTp4I can see why it trips up newcomers, but it feels pretty basic otherwise.
The fact that I can pass a slice to a func 'by value' and mutate the source slice outside the func is already surprising behavior to most people. The fact that it MIGHT mutate the source slice depending on the slice capacity is the part that really drives it home as bad ergonomics for me.
Overall I enjoy working with go, but there are a few aspects that drive me up the wall, this is one of them.
Make it so you can create copy-on-write slices of a larger slice, and a huge number of bugs go away.
Or do what rust did, except at runtime, and keep track of ownership
s := []int{1, 2, 3}
s[0] = 0 // fine, s owns data
s1 := s[0:2] // ownership transferred to s1, s is now read-only
s1[0] = 1 // fine, s1 owns data
s[0] = 1 // panic or compiler error, s1 owns data, not s
With of course functions to allow multiple mutable ownership in cases where that's needed, but it shouldn't be the default