8 ms·
Rust vs Go: A Hands-On Comparison
- tgz 3y agoHow long did it take to develop each web service? Which rps produces?
- jerf 3y agoIn terms of RPS, this web service is more-or-less the fortunes benchmark in the techempower benchmarks, once the data hits the cache: https://www.techempower.com/benchmarks/#section=data-r21 https://www.techempower.com/benchmarks/#section=data-r21 Or, at least, they would be after applying optimizations to them. In short, both of these would serve more rps than you will likely ever need on even the lowest end virtual machines. The underlying API provider will probably cut you off from querying them before you run out of RPS.
- mre 3y agoAuthor here. It took me about 2 hours to code the Go service and maybe 4 to do the Rust part. The reason why Rust took longer was mainly because of the better error handling and the few more higher level abstractions. I could have added unwraps everywhere to get about the same experience, but yeah.
- c2xlZXB5Cg1 3y agoHonest question: how does "better error handling" cause the implementation time to increase?
- Siecje 3y agoBetter error handling in the final result.
- ianpurton 3y agoRust supports more software engineering best practices than Go. Some people say the learning curve for Rust is steep. But, in a way you're learning how to write safer code using the latest in language techniques.
- olah_1 3y agoI wouldn't say that reference counting systems are a "software engineering best practice". Mainly because the engineer is not consciously doing anything themselves. It's not like a builder pattern or something. They are just doing what the compiler wants them to do. In fact, there are many ways to get around the compiler errors that feel like an anti-pattern (like using `.clone()` everywhere). There are other ways to write safe code than just reference counting systems. And arguably, a software engineer would learn more best practices by managing memory more directly.
- jjtheblunt 3y ago"best practices" however seems a context dependent term.
- guilhas 3y agoKubernetes also has a steep learning curve, and does everything you could dream of, that your docker or managed cloud does not Do you need them? Will you enjoy using it?
- packetlost 3y agoIMO the problem is Rust is very opinionated about how you structure your application, which might not actually match up with what the most logical structure for a particular case is. It depends.
- metaltyphoon 3y agoYeah, i have a distaste for every file is a module approach.
- hu3 3y agoI found the article quite unbiased, specially coming from a Rust focused platform service. So congrats! Relevant part FTA: Which language is right for you? Go: - easy to learn, fast, good for web services - batteries included. We did a lot with just the standard library. - Our only dependency was Gin, which is a very popular web framework. Rust: - fast, safe, evolving ecosystem for web services - no batteries included. We had to add a lot of dependencies to get the same functionality as in Go and write our own small middleware. - the final handler code was free from distracting error handling, because we used our own error type and the ? operator. This makes for very readable code, at the cost of having to write additional adapter logic.
- leetharris 3y agoJust some random guy's anecdote. I learned both of these simultaneously in two quirky ways. I learned Go using the "Learn Go with Tests" book https://quii.gitbook.io/learn-go-with-tests/ https://quii.gitbook.io/learn-go-with-tests/ I learned Rust through a book called "From JavaScript to Rust" https://github.com/vinodotdev/node-to-rust/releases/download/v1/from-javascript-to-rust.pdf https://github.com/vinodotdev/node-to-rust/releases/download... The thing that stood out the most to me while learning these was their focus on composition over inheritance. Even though I really like C-based languages, I have never liked the inheritance push from the 90s-2000s. I really like some of the things in Go, but overall I felt I just didn't need it. .NET, Java, etc are more mature with comparable or better capabilities across the board. It's just hard for me to justify Go outside of stylistic preference. I really, really like Rust but ever since learning it almost 2 years ago, I still have not found a single use case for it. It's partially the maturity of the competition, but it's also that even in HPC, ML/DL, etc I've done just fine with the "legacy" stack. I would love to hear from people what their thoughts are on this. What are some scenarios where you see these languages thriving? What makes you want to use them? What's the best use case? Would love to evolve my thinking.
- mongol 3y agoI don't think Java has better capabilities across the board compared to Go. Go's ability to be compiled to a single executable is a strength that Java does not have. I learned Go after reading a post on HN about someone that used Go as a strong tool language, instead of shell script. I partly agree with that, and Java is not that either.
- barelysapient 3y ago100% this. Not worrying about the runtime environment is a huge improvement. And with go embed[1], that single binary can be literally _everything_ needed by the application. Even non-go stuff can be embedded, like for example, a compiled React application. [1] https://pkg.go.dev/embed https://pkg.go.dev/embed
- 3y ago
- dingnuts 3y agoWhy are there so many comparisons between Rust and Go? They don't seem to be particularly similar languages, either in use-case or style, but it often feels like the community wants there to be a competition between the two, which doesn't seem appropriate to me. Go should be compared to other high-level garbage collected languages: Java, Python, C# -- that kind of thing. Rust should be compared to C++ and C, but I'd also love to see it compared to other languages with advanced type systems, or contrasted with languages with more traditional or simple type systems.
- deleted 3y ago[deleted]
- jjnoakes 3y agoI find that for many projects I start, I seem to want: - static types - small, static binaries for deployments - lower memory requirements and value semantics where appropriate - a reasonably large ecosystem - cross-platform with strong support for linux/cloud These two languages usually end up at the top of my list when given these requirements.
- sophacles 3y agoThere's all sorts of ways to categorize languages. Why group by "high-level" (whatever that means) and gc? Why not say "why compare rust and go - go belongs in the minimal type system category with zig, C, python, lisp. rust belongs in the inferred thpe system category with haskell, ocaml, f# and so on"? Or (as is the case with the article), why not group them by popularity for a domain/task and do a comparison in that light?
- lucasyvas 3y agoI think the comparison comes because Rust has fairly solid high-level ergonomics for a systems programming language, which allows to to "bleed" into comparison with traditional higher level languages even where one might initially think it doesn't belong. However, it's ultimately designed for systems programming so it will be hard for it to win on higher level ergonomics evolving in languages like Go or other. I think they also get compared because they attract programmers that value similar things, just coming at it from two different directions.
- barelysapient 3y agoI liked the article but I have a nit with the Deployment section: First, Go has other deployment options other than Docker or providers that support docker containers. For example, Google App Engine (GAE) has a command line interface very similar to Shuttle that enables deployment and hosting of your Go application. This is probably the best 'apples' to 'apples' comparison. Go and Rust are very similar in this way because both can be compiled to a statically linked binary with zero runtime dependencies[1]. This is important because both Javascript and Python are interpreted, frequently with run-time dependencies in addition to the interpreter, that can make hosting and deployment more difficult. Since both languages compile to a statically linked binary, they can be hosted virtually anywhere. Any old linux box or VCS with a modern os should work just fine. Optionally, one can serve from behind Nginx or Caddy for extra functionality. I've chosen this approach on a side project. It deploys with a simple `make deploy` command that runs tests, creates the static binary, and deploys the build asset over SSH and cycles the systemd service. It can be rolled back with `make rollback` which reverts by simply changing the symlink and restarting the service. [1] Assumes a modern OS.
- mighmi 3y agoRe: [1] don't both work bare metal, without any OS?
- barelysapient 3y agoSure, but my reply is in the context of deploying and hosting a web application.
- steve_adams_86 3y agoI’m in a weird spot where I like both equally, but for slightly different reasons. When I have a dumb idea (they’re all dumb), depending on what its core features are, I’ll prototype it in Go, TypeScript, or Python. Inevitably I’ll want to or I will actually rebuild it with rust once it’s relatively stable. It’s like Go, TypeScript, and Python are pencil and rust is ink. Go is my favourite for strictly back end prototyping, but I don’t love it for processing data. That’s where Python shines. Then sometimes I’ll use a full stack framework like Next or Remix and build a backend there, but transfer it (or important parts of it) to rust eventually. My favourite deployment is a totally static site with a rust backend. But it’s equally easy with Go, and the initial development with Go is always way faster for me. It’s just the long term maintenance story that I don’t love with Go. Also, I prefer Rust’s type system quite a bit. It could just be intuitive to me. I do find several teams I’ve worked with on Go projects have not actually understood the Go type system, and they’ve run into pain as a result very regularly.
- jerf 3y ago"I’m in a weird spot where I like both equally, but for slightly different reasons." It's not weird. Weird is "I have a language I use for everything. Quick scripts! Network servers! My shell! Games! Batch processing CSV records from random FTP servers! Everything! Any implication that my language is not the best choice for all these things must be met with maximum hostility!" is the weird thing. Popular, but weird. A professional programmer should have at a bare minimum a static language, a dynamic scripting language, basic shell familiarity (be it Unix or Powershell, I'm OS agnostic here, but you should be able to use whatever it is), and probably at least some basic SQL of some sort. And as your career advances, it'll only grow from there.
- ilrwbwrkhv 3y agoYa thanks for showcasing both. As is evident from the code, Go is simpler and cleaner. In 3 months it would be much easier to reason about than the Rust version. I am happy with our decision to migrate to Go from Common Lisp. I also wonder what is the long term memory usage of the Rust system. With Go I have found very little memory use which remains constant for months before machine swap.
- 0xDEF 3y agoWhat is CL? Google says it's an ancient IBM language which is probably not what you meant.
- ReleaseCandidat 3y agoCommon Lisp?
- ilrwbwrkhv 3y agoCL is Common Lisp. Sorry for the abbreviation. Updated.
- tuetuopay 3y agoWhile the Rust in this article could be a bit better (though that's nitpicking and explicitly not the point), I find it showcases the biggest difference between the two: once you've laid out the foundations which takes a bit of time, the resulting code is a lot leaner. The handlers are short, concise, and the separation of concerns is natural. Good job the shuttle team, I now want to try your service :D The thing I feel like misses from the article is the more "second thought but vital" stuff, like observability. The tracing ecosystem for Rust is a blessing, and so boilerplate-free that you sprinkle it all over and never forget about it. From experience, having to manually access the span, put data in it, and defer close, that people don't bother doing them. Yay macros! With Go it's really easy to throw stuff at the wall, and it'll stick. Rust encourages you to put glue on it before throwing stuff. Also once you get used to axum and the various extractors, be warned, you'll hardly go back. It's so natural and powerful.
- mre 3y agoGreat point! That could be the subject of a follow-up article. Noted!
- mighmi 3y ago> While the Rust in this article could be a bit better (though that's nitpicking and explicitly not the point), How so? While the Go seems very high quality, they are a Rust shop so I assumed the Rust is even better (I don't know Rust).
- tuetuopay 3y agoThe one thing that stood out was the custom error type that wraps up Anyhow (which is a glorified Box<dyn Error + Send + Sync + 'static> aka void* for errors). What would actually be done for such an app would at least be one variant for each error type you can encounter (e.g. Db(sqlx::Error), Api(reqwest::Error), ...) with something like thiserror. Then in the conversion to Response, there'd be a (admittedly) large match that maps out every error kind to a response code. In this example, they conflated both the external api request and database error to a 500, while before they properly made the api request a 404 (which is not always the case), and the db error to a 500. For real apps you gain a lot. By doing this legwork, you get terrific developer experience as virtually all errors that can happen get mapped to a proper status code and handled by a single `?`, and terrific user experience as all errors ... (you get the idea). Don't get me wrong, that's still an improvement! They do show it is easy to have the infrastructure to do so. In the go version, there is literally no thought given to this. Sprinkle return c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) everywhere and done!
- danesparza 3y ago"Being originally created to simplify building web services, Go has a number ..." Um. No. Go was originally created at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson. Some key reasons for its creation include: Improving productivity of Google engineers - The creators felt existing languages like C++ took too long to compile and had other productivity issues. Go was designed to be simpler and faster to compile. Supporting concurrency and multi-core machines - Go has built-in concurrency features like goroutines and channels to allow easy parallelism and concurrency on multi-core machines. This was becoming increasingly important with multi-core CPUs. Improved code readability and maintainability - Go was designed with a clean, minimal syntax that avoided complex features like inheritance in favor of simplicity. This improved code readability and made programs easier to maintain. Better performance than interpreted languages - While Go is a compiled language like C++, it was designed to provide performance closer to those languages while maintaining some productivity benefits of interpreted languages like Python. Supporting networked systems and servers - Go has good built-in support for networking and servers, making it well-suited for building networked systems, web servers, and other server tools. References: https://go.dev/talks/2012/splash.article https://go.dev/talks/2012/splash.article
- jjtheblunt 3y ago> Go was originally created at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson. that's possibly Google marketing hyperbole : check out what the guys were doing just before changing from Bell Labs to Google. https://en.wikipedia.org/wiki/Limbo_(programming_language) https://en.wikipedia.org/wiki/Limbo_(programming_language)
- geodel 3y agoHuh, only Pike is listed not the other two on Wikipedia link. So how is it Google marketing hyperbole?
- jjtheblunt 3y agoInvented at bell labs and even in use but refined once at google
- qaq 3y agoRust async story is still playing out. Go green threads approach is pretty solid. My go to stack for personal projects: Go SQLC for db access https://github.com/a-h/templ https://github.com/a-h/templ (kinda JSX for go) for templating HTMX Postgres I have a feeling Mojo will ship 1.0 before Rust finishes sorting out async
- Animats 3y agoBoth are amusingly over-complicated. You don't need a database, just an in-memory cache. All you're storing is recent weather info, which you get from an external server. Caching is just to prevent hitting the external server too hard. Then the program becomes a single-file executable, which means you can get rid of Docker. If you set this up to run under FCGI, it can run on some $5/month shared hosting account.
- zlurker 3y agoI think this over complication makes the comparison more realistic. Yes for this exact example it's too much, but it gives a better understanding of what a more complex real-world example would look like.
- dgb23 3y agoYou can get rid of Docker either way.
- xboxnolifes 3y agoThis isn't a case study on how to build a weather app. It's okay to include unnecessary features to showcase how the language uses them.
- blindseer 3y agoThat seems like a fair article. The error handling in Go is SO verbose. When reading my code (or even reviewing other people's code) in order to understand at a high level what is going on, I feel like I'm squinting through a mesh wire window. Compare this example in Go: city := c.Query("city") latlong, err := getLatLong(city) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } weather, err := getWeather(*latlong) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } weatherDisplay, err := extractWeatherData(city, weather) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.HTML(http.StatusOK, "weather.html", weatherDisplay) To this code in Rust: let lat_long = fetch_lat_long(¶ms.city).await?; let weather = fetch_weather(lat_long).await?; let display = WeatherDisplay::new(params.city, weather); Maybe on first glance the Rust code can seem alien (what is a `?` doing there, what is actually going on with `.await`, etc) but when you are writing a 100k line application in Rust, you learn the patterns and want to be able to see the domain application logic clearly. And yet, there's no hidden errors or exceptions. When this code fails, you will be able to clearly identify what happened and which line the error occurred on. Prototyping even small applications in Go is verbose. And worse still, error prone. It's easy to be lazy and not check for errors and oops 3 months in your code fails catastrophically. I know a lot of people like Go on here but in my mind Go only makes sense as a replacement for Python (static compilation, better error handling than Python, faster etc). If you don't know exactly what you want to build, maybe it is faster to prototype it in Go? I still would reach for Rust in those instances but that's just me. For large applications there's no question in my mind that Rust is a better choice. Edit: people have pointed out that I'm not comparing the same thing, which is true, I apologize for the confusing. But even code where Go propagates the errors, it is much more verbose (and my point still stands) err := db.Get(&latLong, "SELECT lat, long FROM cities WHERE name = $1", name) if err == nil { return latLong, nil } latLong, err = fetchLatLong(name) if err != nil { return nil, err } err = insertCity(db, name, *latLong) if err != nil { return nil, err } And this is extremely common. More discussion in the thread below.
- deleted 3y ago[deleted]
- kunley 3y agoNot to ignite a flame war, but to make a point about daily bread activities: what was the compilation time of both projects, esp. after using crates like 'anyhow'? How long did it take to have a full fix-build-test cycle in both languages? How many refactorings per time unit could a smart coder do in both ecosystems?
- estebank 3y ago> How many refactorings per time unit could a smart coder do in both ecosystems? I've found that refactors are more involved in Rust (because of some "viral" features that require changing code in a lot of places, adding lifetimes to a type being the most obvious example) requiring more effort from the developer, but that after handling the complaints from the compiler the resulting code is usually properly ported to the new design. This has not been my experience in other languages, particularly dynamically typed ones. It is partially helped by some design patterns that are prevalent in Rust libraries, like typed builders or more complex trait bounds that ensure correct usage of the exposed APIs.
- intalentive 3y agoThe Go syntax is so much comfier. Never written in Go but it looks attractive, might have to give it a spin.