Hacker News new | ask | show | jobs
by fiedzia 1652 days ago
> Is it a better bet to move on and go full Rust, rather than bother with wherever the goggle golang train is headed?

Ecosystem aside, with Rust you'll get far better language design (generics fully integrated with everything (stdlib, consts, all libraries), sane error handling, sane dependency management, no "any" type, editions, and more. More complexity too though.

> Certainly some problems will benefit from the addition of generics, but is it really enough to justify the added complexity?

Coming from languages that have them, it's just hard to take Golang seriously, where every library either ditches type safety (more runtime errors I wouldn't have with other language), or forces you to copy-paste code just because you need support for some new type (more boilerplate to maintain == more errors). Or reinvents generics with code generation and aboriginal characters.

Once you start using generics, they really aren't complex.

6 comments

> Coming from languages that have them, it's just hard to take Golang seriously

Some people judge the language on their ability to get work done with it.

I get generics, but they really don't come up in daily use with the tasks where I use Go. Yes, it would be nice for writing some libraries but you're going through a laundry list of Rust features which don't hamper my ability to get work done at all.

I even like Rust, but if I'm going to write a worker that will read from a queue, do a transformation and write to a few more queues/services upon completion, Go just works and the turn around time is far better than Rust. It's like was Perl was for Unix, but for the cloud instead.

Agreed.

I wrote a bunch of Rust, Scala, Haskell, but I still greatly prefer Go, even without generics.

I am very happy with the generic container libraries I'll get with generics, but I hope people won't try to be too clever (as they usually do). So far, Go is a language that just rules out a lot of bikeshedding, which I very much appreciate. We'll see how that evolves.

I do think premature or wrong abstractions are a much bigger and widespread problem than a lack of abstraction.

Also, I really like Go's error handling, it results in error messages in Go projects usually being top-notch (because of explicit handling and wrapping which includes relevant context and human-readable messages).

I'm with you. Go has its problems, and I am aware of them, but it is just so compatible with the way my brain works, it is amazing.

I remember listening to a podcast about C++, and the guest explained how after working with C++ for about five years, they still encountered aspects of the language that surprised them on a regular basis (to be fair, though, that was before C++11). To me, Go just clicks in way few languages did.

I think language preference is very much about how people’s brains work (and they all work differently!). My brain struggles with Go, but clicks with Rust.
This must be it and ditto for me on Rust. It feels like they made the language just for me.
Interesting - I've also written a ton of code in those languages (not as much in Haskell), and Go would be my last choice by a large margin (and Rust my favorite by a wide margin). I went through a love/hate relationship for a while (it lets me create static executable, compiles pretty fast, it is easy to remember, etc. but if I have to type "if err == nil" one more time!!!).

While I applaud the focus on simplicity, I found it simply transfers that burden to the programmer (I have to loop over a map to clear it...really?). Every single "lack of" feature in Go (has nil, no sum type errors, no pattern matching, etc.) is in Rust which gives me endless freedom to express safe, correct programs. I suppose language choice is highly individual, but it still perplexes me as to what people see in Go over Rust.

I'm sure everyone's experiences differ, but in my travels Go has shown to carry the lowest rate of "who wrote this shit?" when you come back to a project five years later. I agree that it puts the burden onto the programmer upfront, but reduces it down the road. Tradeoffs...

I have a lot more fun writing code in other languages. I enjoy not having that burden on me while writing code. In an earlier life that would have been important to me. Now that I'm old and curmudgeony I've started to value other things.

I work in a team, where not all are unicorn Rust specialists. The work we do doesn't require millisecond-level response times, nor is it something that needs to be ultra-safe in relation to memory safety.

I can teach any mook with basic Java/C# programming knowledge how to be productive in Go in less than a week. At this point they can read pretty much any Go code pretty fluently and can be trusted not to commit anything stupid.

Can you say the same about Rust?

> I work in a team, where not all are unicorn Rust specialists

> I can teach any mook with basic Java/C# programming knowledge how to be productive in Go in less than a week.

This is fair, and probably the reason why Go continues to be popular I guess

> The work we do doesn't require millisecond-level response times

Rust is a high level language, and it is a bit of a misnomer that it is only good for low level things. Most of my stuff doesn't require this level of speed either (the previous major version of my project was written in _Python_). I use Rust for the safety and data structure benefits, not speed.

> can be trusted not to commit anything stupid

As a Rust coder, and a fan of functional programming as well, I personally find any "null pointer" error quite "stupid" and unnecessary as is the occasional "err == nil" instead of "err != nil", or forgetting to check it at all. We will probably have to disagree on what constitutes stupidity, and that is fine.

Absolutely. You can write some Rust by following some hello world tutorial and be productive in 15 minutes. That doesn't mean you'd be proficient in writing procedural macros or understanding all details of memory management, but I've seen Linux magazines presenting Rust omitting all those "advanced" features and surprising amount of Rust users is new to programming.

The learning curve is usually different because you will need to understand more before program will compile and because most materials aims to cover 100% of the language from day one, but if you want to approach it differently, productivity wouldn't be a problem.

Just so we're clear, you're saying that Rust only takes 15 minutes to learn and be productive in, for the average person?
Premature or wrong anything is clearly bad. But is parameterisation a premature or wrong abstraction? I'd say that parameterisation - even in the type language - is the original proven abstraction.
I think this is why I always have trouble understanding arguments against generics (in the general case at least). Parameters are added to functions/methods in order to make them more generic/flexible, as a rule. To move something that may have been hardcoded into something that can now be configured:

  get_data() { filename=default ... }
  =becomes=>
  get_data(filename=default) { ... }
When the type either does not matter, but the concrete instance records it, or the type makes sense to be configurable, you want generics. As a silly example (but short enough to fit into a comment block):

  fn nth(seq: [int], n: int) -> int
Now you have to make a new nth for every single sequence type, even though it has no bearings on the actual operation. Or you make it generic:

  fn nth<T>(seq: [T], n: int) -> T
That's a trivia case, yes. But if anyone has ever worked on a complex code base there are plenty of situations like this that turn up, at least in my experience. Sure, I almost always start off with concrete instances with a fixed type, but as soon as it becomes apparent that the type itself is irrelevant and I have a couple use-cases with different types, why not make it generic and be done with it? Like, would you really have more than one version of that get_data function running around, one for every conceivable filename? That would be obscene. Why would you do the same with types?
Yes, in my opinion oftentimes it is.

There is a reason why even in mathematics people like to operate on concrete examples to get an intuition. For many, concrete is much easier to understand than abstract.

That's the less important point. The more important point is that making your code generic often involves more trickery which makes the code more complex, even if you only use the code once or twice - so that's just effort wasted.

The fact that parameterization is a proven abstraction doesn't mean it's good everywhere. Same as I don't agree with the "Clean Code" way of creating a myriad of 4 line functions.

Yes, good programmers won't make these mistakes, you can totally handle them. But when arriving at legacy code or open-source projects I greatly prefer to find under-abstraction rather than over-abstraction.

To be clear, I'm not against generics, I just agree with the parent of my original message. I'm worried people will overuse them and I don't want a whole laundry list of Rust features in Go. I'm very happy about libraries with type-safe generic B-Trees.

I get what you mean, and I personally rarely write generics, but when I need them they're great. I think that a good rule of thumb that would be easy to implement and review would be "no generics outside of libraries". This way, you get your type-safe containers, your application code doesn't get much more complex than before, and if you want to introduce generics, you have to really think about it.
> I get generics, but they really don't come up in daily use with the tasks where I use Go.

I sort of understand this argument, but I can't really imagine defining queue as something else then <T> wait_for_item() -> T. I've been writing Python for to long to know that wait_for_item() -> any will backfire in production eventually and I don't want that. At some scale (both in code size and amount and scope of dependencies) those problems just become too serious and too common to not have language that deals with that. And Go is way too popular for people to limit its use only for the cases where it currently works.

Define queue using generics, sure. It will be huge for people waiting packages.

A specific queue is typically well defined and has a struct in/out.

There are times when I've wanted arbitrarily nested JSON that doesn't map into structs very well, but it is uncommon enough.

I guess a better response to this is that I don't use generics a lot when writing Go, but I probably use a lot of packages where they would have been incredibly useful.
> if I'm going to write a worker that will read from a queue, do a transformation and write to a few more queues/services upon completion, Go just works and the turn around time is far better than Rust.

Since the inception of async/await in Rust, it is incredibly quick to whip something like that up. The slowest part might even be the time it takes to compile. Maybe that's what you were referring to?

The way Go does it is easier to reason with, channels and goroutines are really easy to explain to anyone.
That does sound a lot easier than Rust's channels and tasks...
> Some people judge the language on their ability to get work done with it.

Really? Safety and correctness aren't relevant for you? Then why even bother with go instead of Python or a Lisp?

For me it's crucial that a programming language contains tools that allow me to definitely rule out as many errors as possible. A powerful (and sound) typesystem does just that.

How are you finding Idris? or are you more in the Agda camp?

If you want to definitely rule out as many errors as possible, dependently typed languages are the state of the art, allowing you to write a sort function that will fail to compile if it returns a list that isn't sorted (eg,https://dafoster.net/articles/2015/02/27/proof-terms-in-idri..., or https://www.twanvl.nl/blog/agda/sorting).

After all, if you can't even prove basic properties about your code from your language, like array accesses being within bounds, are you really using all possible tools to rule out errors at your disposal?

To be fair, I never had a chance to use Idris nor Agda. I don't know how capable I would be to encode the proofs in libraries nor client code. If it's usable, I am all for it.

Otoh, I do know that languages like OCaml, Haskell, or Rust take the burden of trivial errors from my shoulders for neglible cost.

Yeah, that was an insincere gotcha question, and it's a shame that it could potentially undermine other readers' potential value of a higher degree of confidence versus a hypothetically perfect confidence.
Does Rust also have a feature that instills a burning desire to proselytise?
I like Rust mostly for its user-friendly tools. I dislike the compilation model. The type system I already liked even before Rust existed. So, not every String opinion on PL is down to Rust.
No, I just find discussions like these absolutely hilarious. A Go developer with ten years of experience convinces himself that Go is the gospel and generics are a useless toy and distraction from "real work™", then tries generics and they move from a feature you don't really need to a feature you couldn't possibly live without.

Same with every other thing that goes into this language. A thing that's been available elsewhere for literally 3-4 decades.

We have some examples in this very thread.

Your description doesn’t match what I see in this thread at all.

Odd that you’re so upset by other people’s choices.

Yes, all Rust devs get infected with the PROSELTIZE virus. Other languages are desperately trying to develop vaccines.
> > Some people judge the language on their ability to get work done with it.

> Really? Safety and correctness aren't relevant for you? Then why even bother with go instead of Python or a Lisp?

A charitable reading of the GP would be that "safety and correctness" would be included in "getting work done", in amounts that are appropriate to the work in question. Your interpretation is... less than charitable.

Exactly. Go is safe enough. Correctness for most programs is more about making sure it solves the problem correctly, not merely that it guarantees no errors if it runs. That feels like a modern version of "It compiles, I'm done."
Maybe they don't want a target directory using 3gb from a clean build.
It's already self-evident that Go manages that just fine. That's just weird dogma.
> It's like [what] Perl was for Unix, but for the cloud instead.

Perl and Go both have the kind of long-term language stability that I value above all.

But Go offers excellent concurrency, networking baked-in, and now even fuzzing.

https://go.dev/blog/fuzz-beta

I use Go every day and yeah you can your work done with it but not quickly due to typing all the boilerplate.
> Some people judge the language on their ability to get work done with it.

I value maintenability (type safety helps prevent unintended consequences) and readability (Haskell and Clojure are out).

I haven't even written that much go, but even with the little I have written, I have felt hindered in my "ability to get work done" by the lack of generics.
That comparison is a disowner for Perl, given its language capabilities.
It's a disowner for Go given that Perl's philosophy is "more is more" and Go's philosophy is the Wirthian "less is more".
There is a market for a language like Rust but with garbage collection and reflection. There is OCaml, but it's not for the modern developer. Go with generics is the closest thing to that which is getting some use.
This more|less also describes Nim. The recent automatic memory management ARC/ORC alternative is not really even what most people think of as "garbage collection". Its macros give you full AST accept & re-emit powers and it's had generics since before Go existed. I realize it probably does not score highly on "gets used", but it deserves more attention.
I think so too but I think the languages that fit the bill are Kotlin and Swift. Both modern syntax, great generics w/GC and ARC respectively.

Go w/generics falls very short IMO, expressibility, type safety and poor null handling all rule it out as a reasonable stand-in for Rust.

> There is a market for a language like Rust but with garbage collection and reflection.

That's why languages like JS/TS, Haskell, Elixir, OCaml (which is way more modern than Go), ... exist and are used.

That language is Swift. It's got a lot of similarities with rust, just with everything being ref counted.

If only its ecosystem was more platform agnostic.

Swift is pretty much that language. It doesn't have the imitable crate ecosystem or tooling, though.
Why not Ocaml?
Ocaml's ecosystem is a total disaster. Do you use the lousy included standard library? Do you use Jane Street Base? Jane Street Core? Async? Lwt? Batteries? Containers? Iter? Esy? Opam? Ocamlbuild? Dune? Have you seen Dune's documentation? And Facebook fractured the ecosystem even more with Reason, obnoxiously.
Slight nit: Rust does have an `Any` type[0] (and has since at least 1.0). Unlike Go's `interface{}`/`Any` type though, it's actually type-safe; the only way to get a value from it is to try to downcast into a concrete type, which returns an Option and will always be `None` if it doesn't match the type.

[0]: https://doc.rust-lang.org/std/any/trait.Any.html

That's generally how interface{} works in Go too. In local idioms, but the same effect. Only valid operations are permitted.

If you continue to disagree with me, please be specific about what operation Go permits on interface{} values that you consider type-unsafe.

The trailing suffix cast (not sure the actual name, but `x.(string)` and the like) are not type safe. Yes, it's not required to be able to try to deal with an empty interface type, but it's there, and it will always compile fine and allow code afterwards to freely assume the cast succeeded without any errors.

I imagine the rebuttal to this is that you could always just manually `unwrap` the `Option` that is returned by the downcast methods in Rust, but I pretty strongly feel that adding explicit syntax for this type of unsafe cast normalizes it to an extent that having an `unwrap` method on a generic Option type doesn't remotely approach. It would be quite a stretch to argue that having the `unwrap` method on Option is explicitly a endorsement on unwrapping on the downcast methods given that Option is used for far more than just that (and especially given the huge amount of stigma that using `unwrap` gets in the community, which is mostly fair but sometimes goes a little overboard). On the other hand, having specific syntax that is used for unsafe casts and nothing else is a pretty explicit argument that it should be done sometimes, or else it wouldn't be in the language at all. Go could pretty easily have gone the route they did with map lookups and had the unsafe casts return two values, the latter of which is a boolean indicating success or failure (and in the cast of `false` being returned, the former value would just be the zero value for the output type), and the fact that this wasn't done means that ergonomics was prioritized over safety.

"Yes, it's not required to be able to try to deal with an empty interface type, but it's there, and it will always compile fine and allow code afterwards to freely assume the cast succeeded without any errors."

That is incorrect. It issues a runtime panic, which is the same as the syntax for Rust that will do the same thing. Or you can use the "x, isX = y.(SomeType)" syntax and it will tell you whether it matches or not.

It's the exact same functionality just spelled differently, but there is no scenario where you have an int but you call it a string and the code simply proceeds along and does whatever.

"unsafe casts return two values"

It does do that! It's done it since the beginning. It's not a cast, though. It's a "type assertion". It can't convert. Go only has casting for safe conversions... well, things most programmers consider safe. I don't consider int -> byte "safe" but I am in the minority on that.

You need to stop talking about Go. You don't know it. There's nothing wrong with not knowing it, but you shouldn't combine that with trying to explain it to people. It isn't as crazy as you think. It is definitely type-safe. The "type safe" that it is is less rich and complex than Rust or Haskell, but it is type safe within its type system, subject to the usual "unsafe" caveat. If it weren't, it would never had needed generics... it would be a dynamic language and they build generics in so deep they aren't even "generics", they're just how the language works at all. The whole reason Go needs generics is precisely that it isn't type-unsafe.

> That is incorrect. It issues a runtime panic, which is the same as the syntax for Rust that will do the same thing.

As I said before, having explicit syntax for it is a very different thing than having a method for it on a generic type that isn't specific to it

> It does do that! It's done it since the beginning

That's good! Still isn't required though, and having a safe way to do something doesn't mean that the unsafe way doesn't exist

> It's not a cast, though. It's a "type assertion". It can't convert.

Okay? It still lets you get errors due to the type system not preventing them

> It is definitely type-safe. The "type safe" that it is is less rich and complex than Rust or Haskell, but it is type safe within its type system

I agree that type safety is a spectrum, and very few languiages are fully type safe. I probably should have been more clear that I wasn't saying that Go wasn't 100% unsafe, but I thought it would be obvious I wasn't saying that. Clearly that's not the case.

> If it weren't, it would never had needed generics... it would be a dynamic language and they build generics in so deep they aren't even "generics", they're just how the language works at all. The whole reason Go needs generics is precisely that it isn't type-unsafe.

I'm not sure what this means; pretty much every statically typed language gets benefits from generics, and they're clearly not all equally type safe, so I don't know what this is supposed to convince me of.

> You need to stop talking about Go. You don't know it.

I don't know everything about Go, that's true. Go doesn't have a special definition of type safety though, and recognizing places where it isn't type safe doesn't require complete knowledge of the entire language.

They are called type assertions, I believe they are type safe, albeit at runtime. It will panic if the assertion is incorrect.
Is Rust really a suitable replacement for Go? I mean I know they are both system languages but I feel they have different use cases completely.
I'm a Rust "fanboy" (if one wants to say so), and I still think that the use cases for Rust are just a minority in the landscape of modern languages (that is, where there is a choice).

Rust has a mind-boggling overhead, for many reasons (and I'm not talking about the borrow checker, which I think one gets used to after some time), even if the language itself is consistent and ergonomic (within the intended constraints).

To me, they have very different use cases - one will definitely know when Rust is required or it's an appropriate pick. For the rest, Go is fine. I think that those who put them on the same basket, haven't really used one of them.

Regarding systems programming, my opinion is that they require the lack of a runtime (not just because of the performance, but also, for the framework(s) written with low-level primitives in mind), but this is arguable (in particular, there's no clearcut definition of what systems programming is).

Also a Rust "fanboy" and I would disagree. I'm amazed how productive I am in Rust (about 6 months in). I've written tons of Go as well, and I write Rust faster (What I was expecting was to write better code, but have it take longer, but turned out not to be true). I also thought the language would be too "low level" for the type code I write (I wrote in Go and Python mostly before, though I've written in many languages in the past), but I found it scales very well up and down to low and high level challenges (Serde _rocks_). At this point, I use Rust for everything except a couple hundred line script (Python for that).

I'm not saying everyone would fit in that camp and it is a harder language to get started in for sure, but I think the borrow checker scares away too many people. It is a learning curve, but when it clicks, you will realize that every language has ownership and borrowing... you just didn't realize it because the GC allowed you to be sloppy about it. Once you do, it makes you a better programmer (just like coding in Haskell does).

> I've written tons of Go as well, and I write Rust faster

The overhead in Rust, when compared to comparable languages, is very concrete. On top of my head:

- using hash maps is more convoluted (in some cases, arrays also need some boilerplate as well)

- bidirectional/circual references need to be handled (and are also ugly); algorithms programming in Rust is typically more complex because of this

- lifetimes (which also spread virally when introduced)

- explicit allocation types

- (smart) pointer types in general

- interior mutability; which may also required additional design (including: will the mutexes destroy performance?)

Some of them intersect with each other and pile up (allocation types; pointer types; interior mutability).

There is certainly overhead in Golang (I think it's not very ergonomic for a functional style, for example), but it's nothing comparable.

Overhead takes time; unless one has a time machine, it makes a programming language concretely "slower".

The above is just the concrete overhead. The abstract overhead (=things to keep in mind) is another story (e.g. proliferation of types because of the ownership, traits...). I understand, say, that path types are a necessary evil, but they're surely ugly to handle.

> you just didn't realize it because the GC allowed you to be sloppy about it

It's not sloppy where it's not needed. A significant part of the Rust overhead is due to the rigorous philosophy of the language, which enforces constraints also when they're not required. This is absolutely fine, but it's not realistic to think that it has no cost.

I thought this as well before I had used the language for major projects, but in practice, I found it not the case (at least for me). Your list of concerns I do not find to be anything I think about day to day as I'm coding. I just write code normally for the most part. Yes, you do have to think about your data structures and how you will use them, but in other languages I found myself redesigning these later because I had come up with the wrong paradigm - in Rust I find myself getting these correct the first time, so perhaps the restrictions I find helpful here (I suspect the earlier poster commenting how certain languages fit your thinking patterns may be on to something).

My Rust code typically 'just works' the first time or close to it (something I haven't experienced since writing Haskell and Ocaml), but in other languages I would not experience this, and I'd spend more time debugging. I have gotten stuck a few times in Rust as part of my learning journey, but overall, I'm still proceeding at least as fast if not faster than I wrote in Go and other languages.

Rust is definitely not perfect, and I see some of the warts, but I don't want to write a major project in anything else at this point.

Ironically I've been using a lot of hashmaps in some code recently...and the rust implementation is pretty damn ergonomic.

Certainly better than C++ or C# IMHO, and even my Python colleagues were amazed how easy it was to work with.

I have only touched Rust & Go but I would only consider Rust where I would previously have used C or C++. The stuff that needs to be fast, low memory, and what not. I see Go as more of a Java. Good for backend systems. Also CLIs.
> they are both system languages

Due to ability to work without stdlib, Rust is system language in a sense Go never will be, to the point Go authors withdrawn this definition.

> Is Rust really a suitable replacement for Go?

It depends on your requirements for the ecosystem. If you need compatibility with Go or Go libraries, than it's not a replacement.

Libraries and support aside, any program written in Go can be written in Rust (and going back to nostdlib, many programs written in Rust cannot be written in Go). If you have required libraries, I'd say it can be written comparably quickly and easy. For example you can have web service returning hello world in 10 lines of code or so in either.

They are popular in different circles, but that is mostly not related to technical abilities. For 99% of of applications, you could pick either one.

Rust is overused where Haskell or OCaml is more appropriate, simply because it people prefer the general quality of it over more mainstream languages even at the cost of putting up with memory management minutiae. Simply put, one rarely every need to use Rust in a better world.

This is a good direction for Go, which will either lead to Go having a better ecosystem, or remove ideological barriers from people keeping on using Go.

> Rust is overused where Haskell or OCaml is more appropriate

Except then I'd have to learn Haskell or OCaml :) As a curly-bracket language programmer, Rust was much easier for me to get into and feels more like home.

I found this closed-mindedness hard to understand -- I don't spend very much conscious thought on the syntax when programming at all -- but for people like you facebook made Reason ML https://reasonml.github.io/

Someone should port OCaml to the Go runtime with a good high-level FFI. It could really give the community a boost.

The actual need for generics is, I think, overstated. If you are writing a very abstract library, maybe, but most problems that I have encountered don't even begin to approach needing them.
> Or reinvents generics with code generation and aboriginal characters.

Tell me you’ve never used Go without telling me you’ve never used Go.

I believe OP is referring to this https://github.com/vasilevp/aboriginal
Yes, I know, but that was a transparent joke.