Soft question, golang vs Rust, productivity

This is a genuine question, not intended to start a flame war, not intended as an April Fool's joke.

For those who dual wield GoLang / Rust, do you often find GoLang to be more productive for certain classes tasks ?

GoLang lacks Generics. Rust has traits. Rust has macros. Rust has procedural macros. Programming in Rust, one can easily fall into a trap one falls into while using Coq (tactics), C++ (meta programming), Haskell (category theory), Scala (scalaz, cats), that of:
"Can I find a more general solution?"
"Can I find a more elegant abstraction? etc ..."
Intellectually, this is great. Mathematically, this is great. For rigor/correctness, this is great. However, if we assume the first prototype is going to be thrown away anyway, this is not necessarily ideal for getting a sorta-working prototype up as fast as possible.

I suspect I am not the only person here who has lost a weekend in advanced-trait / procedural-macro black magic, only to wonder "wtf was the original problem I was working on again?"

Does this sound completely ridiculous, or have others also run into this experience ?

14 Likes

Possibly, yeah. That can definitely happen. However, my personal experience is that the added benefits of a strong type system far outweigh the drawbacks of such a potential "trap". Of all the languages I use regularly (C, C++, Swift, Python, and Rust) I find myself to be the most productive in Rust. Yes, even compared to Python, and in my experience that can also be attributed to types, even if only in part.

I pretty much miss generics from any language that doesn't have them nowadays. I have no trouble navigating interfaces, and I very much enjoy the additional guarantees I get. I don't think they are a productivity pitfall in Rust.

Where you probably can do better in another language is compile times; for prototyping, Rust's long compile times might be a significant source of annoyance.

14 Likes

I have not written much Go so I'm probably not someone you want to answers the question. Worse still I have never even seen AGda of Coq. However...

I also never fall in to the generic solution trap you describe. In decades of programming I have almost never written a generic anything or had a view to reusability in mind. Every thing is a "first prototype" as it were. If it turns out a similar solution is required later then that might be derived from that prototype in a more general way. That has not happened to me much.

This is probably not great, I end up writing Rust as if it were some kind of C with a lot of nice luxuries added like C++. But hey, it works, it performs well, people are happy.

So I'm not seeing a huge difference is productivity between Rust and C++ and what little experience I have of Go.

Most of the time spent on a solution goes into the thinking of how to solve it. Casting that solution into an actual language is not the major road block.

Many point to Rust compile times as a drag on productivity. Certainly they can be slower but I have never seen that of major significance, besides any time lost there is made up later in testing / debug time.

5 Likes

Agda/Coq is not a requirement for answering the question. One can run into similar issues in Scala (scalaz, cats), Haskell, C++ (template meta programming).

I did "fall" into that trap while learning Rust and exploring what is possible.
Then again I wrote a remote object protocol for Python to see if I can beat the usual protocols ...

2 Likes

I can't talk about Go much (done very little -- lack of generics is a problem), but I do use C# and I think it's relevant enough.

For context, I have tons of experience with C#, and far less in Rust. With this in mind, C# definitely feels faster to get stuff done initially. This is partly down to more experience with it.

But it's also because the C# standard library includes the kitchen sink, so there's a lot less poking around finding crates and documentation. Async is also much easier. The equivalent of tokio, actix-web, hyper, reqwest, etc are all built in. And you don't do as much thinking up front -- put together some classes and interfaces, throw some exceptions when stuff goes wrong, and you're up and running pretty quickly even with fairly involved async code.

Having said this, I don't think it is actually more productive. There are lots of hidden costs.

When the codebase reaches a certain complexity, I (or we when there are several contributors) forget about all the implicit or explicit design assumptions made, and start discovering runtime errors. Sometimes it's quick to fix; sometimes is takes hours of digging. With Rust, you tend to find more (even most) of these at compile time. Not business logic, but the stupid things: nulls, casts, exeptions, etc. Rust feels slower initially, but it's a lot closer to working correctly the first time.

Refactoring can also be more error prone, especially when you're dealing with a bunch of classes and interfaces because you couldn't just use an neat ADT with pattern matching. Side effects and mutability are another footgun when refactoring.

Another pain point is performance, at least for my use case. A lot of the code I write is very latency sensitive, and a good third of my time is spent on making sure that the code performs well enough. This is actually quite hard in a garbage-collected language. A lot of effort goes into re-designing stuff to reduce heap allocations. And the resultant code, while fast, is a lot harder to maintain -- it's not just plain, idiomatic C# any more: some of it looks more like C. With Rust, it's far less of an issue: idiomatic code is genuinely fast, and without GC surprises.

So while I do think C# is quicker for hacking together a prototype, I'm starting to think that the full lifecycle cost might be tilted in Rust's favour. The hidden costs seem to be less. Provided you don't go down every rabbit hole available :slight_smile:

32 Likes

Hmm... Despite having used C++ for a long time I have studiously avoided ever having anything to do with template meta programming. When things get too meta, I hit my abstraction ceiling. That and the ugly syntax crashes my visual cortex or something.

I suspect the same will happen with Rust macros. Something I keep meaning to get around to studying properly....

2 Likes

I feel like the philosophies of the two languages are so opposite that I don't know how often people willingly dual-wield them. At least, I've never met anyone who was a big fan of both -- they seem to either much prefer Go's style of simplicity, or much prefer Rust's style of precision.

I'm not sure that's really the language's fault. In a prototype, one should be reaching for "well I'll just clone it", not "how can I refactor things to allow borrowing here" (unless lifetimes are the core of what you're prototyping).

5 Likes

I have used both Go and Rust in 5-10 small projects that have real world use for me.

Rust certainly feels like it takes more of your energy in the startup phase:

  • What data flow do I want?
  • Stack or heap?
  • Lifetime struggles
  • etc.

But in the long run I do feel like Rust has served me better.

The thing about Go is that it seems simple on the surface but has some really irritating things that really grind my gears:

  • I hate writing if err != nil on every fifth row.
  • Go interface instances can contain nil values that crash your program so you have to check if the thing you are nil checking is an interface first and then check if the internal value is nil. Wow.
  • Goroutines are not thread safe if you share pointers through a channel.

You can definitely get great mileage out of Go, but there are a lot of GOtchas that you need to always be weary of.

I do think that the Rust proponents saying "if it compiles it runs" aren't right, at least as far as "runs" means bug free which it obviously is not.

But obviously you're asking on a Rust forum so obviously people here probably like Rust better :slight_smile:

12 Likes

Valid point. I do not know of many (any) either. Part of what motivated this question is that I was skimming source code for novel database / distributed systems -- expected to see most of it in Rust/C++, but was surprised to see GoLang all over the place.

1 Like

waves :slight_smile:

I have a lot of complaints about Go that I've talked about before on this forum. I have fewer complaints about Rust. But I still like Go a lot, and in particular, its "simplicity" (for my definition of "simplicity") appeals to my sensibilities personally.

10 Likes

@BurntSushi I really like this quote from What made you choose Rust over Go? - #7 by BurntSushi

I actually try hard to blend some of those goals that I find appealing into Rust code that I write. It can be very difficult at times. In general, I do my best to avoid writing generic code unless it's well motivated, or manifests in a way that represents a common pattern among all Rust code. My personal belief is that Go's lack of generics lends considerably to its simplicity. Writing in Go code heavily biases towards less generic code, and thus, more purpose driven code and less YAGNI code. I don't mean this to be a flippant comment; the best of us succumb to writing YAGNI code.

It expresses much more clearly what I was trying to state.

3 Likes

I guess it happens but I don't recall any Rust proponent claiming that if ones Rust code compiles then it is bug free. Obviously not true as you say.

What I do see often are statements to the effect that if ones Rust code compiles it is more likely to be correct. Which I feel is true. Not "correct" but "more likely".

The important point for me though is that when ones Rust code does have bugs they seem to be a lot easier to find.

In many languages there is no chain of cause and effect discernible by inspecting the source code. A bug in function A can show up in the behaviour of function B. Ones bugs can misuse memory causing random data corruption, race conditions and mysterious crashes. Those symptoms may show up in parts of your code unrelated to the location of the actual bug, they may show up a long time after the actual bug did it's dirty work.

Which I suspect is why debuggers are popular tools in those worlds.

On the the other hand Rust does not allow such memory misuse bugs to hide unnoticed. They cannot cause other unrelated parts of ones code to fail. There is a chain of cause and effect discernible in the source code.

4 Likes

I love it. I thought I was only me that shied away from such generalisation, generics, encapsulation and thoughts of reuse. Typically I have the feeling it would take me lot longer to figure out how to generalise something than to cut, paste and tweak it as needed. And the resulting code, now weighed down with the syntax and semantics of generics, templates, whatever, would be a lot harder for anyone to read.

Never heard "YAGANI" before. Apparently its a thing in the Extreme Programming world. Itself something I know nothing about.

I am not alone. I feel much better about myself now. Thanks.

3 Likes

It happens to me in Python too.

Thank you for an honest question and not starting a flame war. For some context, I have been programming for about 4 years, starting with Python, then Javascript, then adding Go and Rust. Here are some thoughts from my end, which probably fall somewhat short of an actual answer. As a full disclosure, I am sympathetic toward Rust and not entirely unbiased.

I suspect I am not the only person here who has lost a weekend in advanced-trait / procedural-macro black magic, only to wonder "wtf was the original problem I was working on again?"

You are in good company my friend. Rust's breadth and depth is a two-edged sword. Sometimes people say Go and Rust are similar in that they both get used for modern microservices (which is true), but Go is fanatical about simplicity and Rust is fanatical about correctness from first principles, which are very different approaches.

Many developers have claimed Go is one of the quickest languages to start being productive in. I suspect they are right. Go has the dead-simple "net/http" package as the de-facto way of creating an http server. Rust takes a package AND more boilerplate to get there. Which one do you use? Actix? Rocket? Hyper? All of the sudden I feel like I need to investigate three potential ways of doing a thing, whereas Go only ever gave me one option.

But that is at the beginning of the project. Toward the end, after you have written several thousand lines of code and start to forget the breadcrumbs you dropped along the way, is where I feel like Rust is in its element. There is no such thing as a nil/Null value that can creep up. All the dependencies seven layers down that could throw an error have to be dealt with explicitly with a Result<>. People sometimes describe the unique feature of Rust as being "fearless concurrency", but I think that is a rather niche concern. I want my code to handle all the "what ifs" I'm not smart enough to keep track of in my head. I am currently re-writing an application that was about 7,000 lines of python in Rust. Things are damn slow to start when you have to select which approach to take, but once you make those choices, the compiler is a rock that pulls you downhill toward completion, not a rock you have to push uphill.

Long way of saying I think the path to productivity likely is faster with Go for the first few hundred lines of code. The things where rust particularly shines (in my opinion) don't really show their full value until your codebase gets larger.

13 Likes

I spent five years writing mainly in Go, then in the last few months have started writing in Rust. In both cases, the language choice was made by the company I work/ed for, not me, so it's not a question of personal preference. I also have Node.js/Typescript and C# to compare to. Because my Rust proficiency is between beginner and intermediate (I haven't, for instance, written any serious macros yet), I cannot really legitimately compare productivity yet, but I do have some subjective impressions. I try not fall into fanboyism for any language or framework, so I hope this is a reasonably fair-minded and unbiased perspective.

I can believe that with time, I may be able to approximate my Go-level productivity using Rust, but that is still a long way off. A few factors contribute to this. Firstly, the in-editor experience has not been great for me with Rust. Partly because macros make it much harder for the language server to do things like "go to definition". Also because, for example, a single C library dependency that I can't compile on my Mac means I have no intellisense at all for the project. That would not happen in Go. Secondly, borrow-checking and lifetime checking are painful for a programmer newish to the language because they represent a new class of compile-time errors - not syntax errors or type errors, which will be pointed out immediately in your editor, but usage errors that relate to how and where variables are used in relation to one another and which only get detected when you do cargo build. Thirdly, the zero-cost abstractions do have a cost, just paid by the programmer rather than the computer. The async abstractions in particular are awkward to use. Try joining futures with return types that look the same but actually have different underlying concrete types, for example. Oh, says my Rust mentor, you need to whack a .boxed_local() on the end of that... Say what?

Productivity in Go is hampered by all those horrible if err!=nil clauses and lack of generics causing you to use a code generator or simply cut and paste functions... but hell, they got concurrency right!

Anyway, I understand how the philosophical starting point of Rust leads to all this, and it's fine. It's a fantastic language in many ways, and I'd choose it over Go for anything high performance or with low-level C interop requirements. But there's no denying these things have and do affect my productivity using it. Compare it with Node + Typescript and the productivity gap is even larger. That said, it also true that the Rust code I write does seem to have fewer bugs than other languages - the no data race guarantee is particularly nice; I've wasted weeks chasing data races in Go.

There's evidence for the case that getting a major project up in Rust is slower than other languages, at least if the developers are having to learn the language - I remember reading about parallel teams developing the same project in Rust, Go and a number of other languages, and the Rust project (all developers were starting as language beginners) was much slower than the others. I wish I could find the reference...

Like I say, none of this is to knock Rust. I'm really looking forward to the day when I can churn out Rust code the way I can churn out Go, because it will be tighter, faster code. But there's no denying the productivity cost of getting there.

11 Likes

Thank you for sharing your experiences.

Are you using IntelliJ? With the latest, I have macros that define new structs and, on 'goto def', IntelliJ, somehow, can jump me to the line where the macro is called (to define the new structs).

Ah, this reminds me. There are certain things in pattern matching easier to do in OCaml/Haskell than Rust, and there are certain things in dealing with anonymous functions easier to do in OCaml/Haskell/Lisp, but where Rust's zero cost abstractions increases programmer work.

I have been using exclusively Go at work from the last 3.5 months but Rust is my favorite general purpose language for my personal projects.

In general I like them both, but I think the philosophies behind the languages are very different. In terms of productivity some of the things I think Go is better at are:

  • Simplicity: I work on a large code base with >50 developers and having a simple language where there is really only one idiomatic way to write the code ensures that there is a general consistency across the board in the way the code is written. Inexperienced developers and people new to Go pick it up quickly and the amount of time until they can actually start contributing in meaningful ways is a lot shorter than Rust.
  • IDE support: Go is better than Rust. I use VS Code for both and while I constantly encounter bugs in rust-analyzer, but I rarely have an issue with Go. I think the gap is closing fast here though!
  • Go interfaces are really intuitive and an easy to use abstraction, and less verbose than Rust traits.
  • Writing goroutines is much easier than writing async Rust.

The following things I think affect productivity in Go:

  • Panics because of nil pointer dereferencing: these are very annoying to debug in production.
  • Lack of compiler output: the Rust compiler really has some of the best compiler output and the Go compiler seems to give as little as possible.
  • External tools to do codegen, in Rust you would just write a macro / proc macro.
3 Likes

I once heard a wise man say "stress is two competing thoughts trying to occupy the same spot in your head". I've always tried to not get dragged down into quarrels over my favorite OS, language, framework etc. But I have felt stressed about choosing Go vs Rust for projects where I am the lead and if things break, everybody knows who to blame. Perhaps that is where some of the angst comes from in online chatrooms. In a way, things would be simpler in cases like yours where you are obligated to use a given language. If you job is to write it in Go, you write Go code. If your job is to write it in Rust, you write Rust. The fact that the tension can even exist means you can get there with either language.

2 Likes