Table of Contents
Get the latest news

Go vs. Rust: Debugging, Memory, Speed & More

Gedalyah Reback | Senior Product Marketing Manager

16 minutes

Table of Contents

Rust and Go in some ways are polar opposites. They are defined by their respective approaches to code. Rust is scrupulous, meticulous, and demands painful attention to detail; Go is laidback by contrast, easygoing, and simple. They both prize efficiency, but one in the means (Go) and the other in the ends (Rust). A true Go vs. Rust battle needs to compare the two languages in more depth.

Golang and Rust owe their births to loathing for other systems languages (and by languages, I mainly mean C++). Mozilla backed employee Graydon Hoare’s side-project for a C++ alternative, which became Rust. Google designed Go to improve productivity and simplify development.

Go vs. Rust: They’re not so different if you pretty much ignore everything about them

But the languages treaded on two very different philosophies to build themselves out. Rust’s creators wanted to improve resource control, security, and concurrency. They achieved this by creating a complex programming language, but one that is indisputably one of the fastest in popular use today. Go went for accessibility, achieving similar goals like high concurrency while gaining a rep for being one of the easiest languages to learn.

The difference in complexity is the main determining factor for a lot of projects – the simpler the code, the simpler the development. But that isn’t the only reason to choose one language over another. This overview will compare and contrast Go vs. Rust with a focus on debugging and observability. We’ll review the most common points of comparison between the two before going into the different options for troubleshooting, debugging, and gaining visibility into apps built with either of the two languages.

Learnability

There’s an absolute contrast here. The languages treaded on two very different philosophies to build themselves out. You can see the difference in how they say “Hello.” Go has only 25 keywords while Rust at the time of writing has 39 keywords (with another 13 reserved for future versions).

Rust’s creators wanted to improve resource control, security, and concurrency. They achieved this by creating a complex programming language, but one that is indisputably one of the fastest in popular use today.

// Hello World! in Rust
fn main() {
    println!("Hello World!");
}

rustc hello.rs

$ ./hello
Hello World!

Rust also uses some different names for key concepts. It refers to its packages as crates. It also relies heavily on macros – macros are pre-processed versions of functions, while functions are compiled, so they involve no runtime. Languages like C, Lisp, Scala, Erlang, and Elixir also use them.

On the flip side, Go went for accessibility, achieving similar goals like high concurrency while gaining a rep for being one of the easiest languages to learn.

//Hello World! in Go
package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

Go does not support macros, but rather constants. The idea is the same – to call a consistent function or value – but the key difference is here:

Rust’s macros are pre-processed. Go’s constants are compiled by the compiler.

Goroutines vs. OS Threads

Go is also notable for its use of a lightweight kind of thread called Goroutines. Goroutines are the most notable example of something called green threading (or virtual threading). They’re designed to work like multithreaded environments but can operate outside of environments that don’t support native threading. VMs or runtimes handle scheduling in green/virtual threads.

Just the same, Goroutines operates with one runtime. But, there being less system overhead because they are managed efficiently by the user-space Go runtime, because they don’t rely on operating systems, helps Go with fast start-up times and fast clean-up times. Additionally, they’re considered incredibly easy to use by simply calling the given functions with the keyword go.

//Seriously a goroutine is this simple
go functiontocall()

Goroutines are particularly efficient because they also allow coordinating data using channels, which can balance the activity of each Goroutine to clamp down on latency.

Rust takes the more traditional approach. To avoid adding overhead by using a runtime, Rust actually dropped native support for green threads and switched to operating system threads early in its history. By default, Rust uses a 1:1 model – one operating system thread to one language thread. As per usual, there are many crate options for using some other kind of model.

Memory Safety

There’s no rust on Rust’s memory. Poor puns aside, Rust has a tremendous emphasis on memory safety and Rust won’t allow for unsafe use of memory during compilation. The practical reason behind this is because most memory errors occur in production and often lead to vulnerabilities; this preempts that.

All data inputs have to have been initialized before use. Rust also doesn’t permit null pointers or dangling pointers.

Go is also considered a memory-safe language, but not to the extent that Rust is. Go also blocks dangling pointers and limits memory leaks.

On memory space, Go uses garbage collection while Rust uses something called ownership and borrowing (every object is owned by someone, who can then lend it out).

Go’s garbage collector periodically works in the background to free up data once you hit some pre-specified value. This will add to system overhead though.

Compiling Go vs. Rust Compiling

That complexity makes the difference when the code compiles. Go compiles straight to machine code, cutting out “middle men” like virtual runtimes or other mid-level interpreters. Other factors, like how Go dependencies work, definitely help with compilation speed.

Rust has made improvements over the years, but still can’t stand up to Go. Nicholas Nethercote, a former engineer at Mozilla, kept detailed notes on the issue and notes continued improvement on benchmarks year to year. See this example of Rust benchmarks in 2019.

Spider-Man compiling Rust instead of fighting crime

Rust sacrifices on compile time in order to achieve strong performance later. Rust’s memory safety implements certain things during compilation time to protect the code from bugs and vulnerabilities. Rust’s macros are precompiled. Rust’s legacy issues related to single-threaded compilation, build scripts, and periodic code generation for individual crates also contribute to slow compilation according to Brian Anderson’s extensive review of the problem, but they are all part of the plan.

That being said, Rust allows for more control of the build process. That might make compilation a longer process, but what comes out on the other side will be far more efficient when running.

Go doesn’t allow for that kind of control automatically. Go’s build tags give you a little control by specifying tags that will decide which files to include and preclude from the build process, however this doesn’t come close to Rust’s compilation customization abilities. Here’s an example with a build tag at the top of this

//+build helloworldtag
package main

import "fmt"

func main() {
fmt.Println("Hello World!")

Include the file in the Go build process with this command:

go build -tags=helloworldtag

Go vs. Rust Debugging: What Do the Devs Prefer?

Go and Rust can be quite different philosophically, but they have similar options for debugging. That being said, they have several options at that. Both languages see large numbers of users leverage print statements, logging, open-source debuggers, and IDE tools to do the job. Here is a quick rundown of the main ones:

GoRust
Print statementsprint()
println()fmt.Print()fmt.Println()fmt.Printf()
println!()
LoggingNative logging, 3rd-party options like glog or logrus..debug macro, multiple logging crates
Built-in or native debuggersDelveRustc (native compiler), Crates: debug_rs crate, multiple debugging crates
Open source debuggersgdb (Go docs) or lldbgdb (fork) or lldb (fork), DWARF
IDEs and Text EditorsVSCode, Goland, VIM, Emacs, AtomVSCode, IntelliJ, CLion, VIM, Emacs, Sublime Text

Rust Debugging

With Rust, you can use print statements [println()], logging at the debug level, or the standard built-in dbg tool to exterminate bugs in your code. Logging carries with it a constant struggle with verbosity (which is the same in other languages): How much data is too much data? Will it make tracking down the problem even harder?

This debug! macro example is partially modified from the one appearing in the Rust docs:

use log::debug;

let pos = Position { x: 3.5, y: -1.5 };

debug!("New position: x: {}, y: {}", pos.x, pos.y);
debug!(target: "location", "New position: x: {}, y: {}", pos.x, pos.y);

There are also several IDEs popular among Rustaceans, but the presumably advanced IDE tooling seems to play second fiddle to the simpler options. In fact, surveys show just that for Rust developers. JetBrains’ own developer surveys showed just that for all segments of IDE users different groups of devs. Some used VSCode, a JetBrains option (IntelliJ IDEA or CLion), and VIM.

Visual Studio Code, CLion, IntelliJ, VIM, and Sublime Text are the most common, with VSCode being the #1 choice for 40% of Rustaceans. Keep in mind though that that number comes from a JetBrains survey of its users, so the numbers for CLion and IntelliJ might be slightly higher than a true average. That being said, CLion and IntelliJ probably do represent a massive chunk of the Rust IDE marketshare.

Go Debugging

Gophers, if you will, actually widely prefer print statements and logs for debugging also, according to a survey by Golang‘s own website. Other features like unit testing and the PANIC output were the next most popular ways to debug Go. About 54% used special debugging tools like Delve or gdb on a local machine. A lot less find Go remote debugging useful, at 16%, indicating that even for simpler syntactic languages, remote debugging can be an issue. While Go is an efficient way to get programs to run smoothly and save time, it’s still bogged down for cloud-native options.

Go uses three kinds of print statements using the fmt package: fmt.Print()fmt.Println(), and fmt.Printf(). You can also use the first two without the fmt package. When it comes to logging, the issue is the same as with most languages, in that it can lead to information overload.

That being said, Go provides a couple of extra log levels not common to other languages via logrus. Altogether, these are the seven Go logging levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, and PANIC.

A common log at the debug level might look like this:

package main

import (
log "github.com/sirupsen/logrus"
)

func main() {
log.SetFormatter(&log.JSONFormatter{})
log.SetLevel(log.DebugLevel)
//To ensure debug logs print with other logging levels, you need the above line of code
//Such a line isn't needed for other logging levels

log.Debug("Debugging message.")

}

In terms of IDEs, there’s a similar selection like Rust for Go users: VSCode, VIM, Emacs, and Atom. JetBrains also has a specialized IDE called GoLand (many surveyed also said they used IntelliJ for Go). It’s not exactly clear what the most popular IDE in the community is.

The two above mentioned surveys are flawed on this question. JetBrains surveyed many of its own users to find 59% preferred GoLand to others; Go found a majority favoring VSCode, but they reached survey takers through a VSCode promo (to be fair, JetBrains and go.dev both admit the issue in their questionnaires). You can see our other post to read more about Go debugging.

Go vs. Rust in Popularity

On a final note, it might be interesting to look at how much developers actually work with (and like to work with) either language. Rust ranks at the top of the list in the annual Stack Overflow survey for most loved language, up there with Clojure, Elixir, TypeScript, Julia, and Python. But Go is high up there also, in 8th place.

On top of that, Rust is the most wanted language by devs who want to work with or learn something else. And Go? Right behind it at 4th place.

Of more than 70,000 devs surveyed, only 11% reported having used Go. But 9% reported using Rust. When you break down those numbers further, you find a very big difference between pro devs and current students. More programmers in training have used Rust (7%) than have used Go (5%)! Could this be a sign of things to come? Are more students skipping the easier languages assuming they can circle back and learn those later? This blog doesn’t have the answers to those questions, but the close numbers between the two in these surveys certainly make comparing them even more interesting.

Conclusion

Go and Rust offer a lot to programmers. They are definitely both products of their time, but reflect that the developer community is itself diverse, seeing divergent paths for going forward. Both languages have their advantages, and both offer some diverse approaches to observability generally and debugging specifically. Both could do though with more advanced options to cope in cloud-native and microservices environments.

If you want to hear the latest from Rookout, sign up for product updates with our monthly newsletter!

Rookout Sandbox

No registration needed

Play Now