There is a lot of confusion out there, when it comes to Go and Rust. People often make comparisons between the two languages and it is very difficult to see the differences until you invest some of your time with each language.
On first glance, Go seemed restrictive and Rust was checking all the feature boxes I wanted in a system programming language.
I started experimenting with both languages and this is what I learned (Spoiler: They don't have much in common):
- Rust is a low level system programming language, very close to C++. With Rust you can write operating systems, kernels, drivers, libraries that work on bare metal, optimize your code with Assembly, etc.
// inline assembly
#![feature(asm)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn foo() {
unsafe {
asm!("NOP");
}
}
// freestanding code
#![feature(no_std, lang_items)]
#![crate_type = "staticlib"]
#![no_std]
- Go is at its best for writing networking code, running in parallel. Go is an excellent choice for applications that have to fetch, parse and analyze remote data. Go is also great for applications that communicate over a network protocol - HTTP, UDP, SNMP, TCP, Raft, ZeroMQ, etc. Go applications are compiled to statically-linked binaries - they have no dependencies.
// goroutines, probably my favorite part of Go
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
data = FetchFromURL()
}()
wg.Wait()
Rust is a complex language and compared to Go - you will need a lot more time to understand the concepts and become productive in it.
With Rust you can do pretty much anything you want. Any feature that you might want to see in a low level programming language - Rust probably has it.
The price you pay for that flexibility is a steep learning curve. If you plan to use Rust in production - some sort of style guide is highly recommended.
// Macros
macro_rules! hello_world {
() => (
println!("Hello!");
)
}
fn main() {
hello_world!()
}
// Pattern matching
let number = 13;
match number {
1 => println!("One!"),
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
13...19 => println!("Between 13 and 19"),
_ => println!("Anything else"),
}
// OOP or Traits
struct Dog { name: &'static str }
impl Dog {
fn wag_tail(&self) {
println!("{} wags tail", self.name);
}
}
// Generics
struct GenericVal<T>(T,);
let x = GenericVal(3i32);
To support all these features Rust uses all the symbols on your keyboard - ampersands(&), dots(., .., ...), colons(:), double colons(::), arrows(->, =>), at signs(@), etc.
The syntax is short is concise - fn for function, pub for public, mod for module, i8 for 8 bit signed integer(-127 to 127), u8 for 8 bit unsigned integer (0 to 255), f64 (64 bit floating point).
From my personal perspective - this makes Rust somewhat unreadable and messy, because you really have to think about the meaning and function of each one of these symbols.
let mut argv = env::args();
let arg: String = argv.nth(1).unwrap(); // error 1
let n: i32 = arg.parse().unwrap(); // error 2
println!("{}", 2 * n);
impl Rectangle {
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn get(&self, k: &Q) -> Option<&V>
where K: Borrow,
Q: Hash + Eq
let mut x: Option = Some(Person { name: Some(name) });
match x {
Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),
_ => {}
}
Rust is not a garbage collected language and yet - compared to C/C++, you don't have to manually manage memory. Rust deals with this problem and ensures memory safety with an unique feature called ownership.
The ownership system is a crucial differentiating Rust feature - you will be dealing with it every few lines of code. You can find a lot of tutorials explaining how that works, but this particular video by Yehuda Katz stands out for me, because it is targeted at people who don't have any experience with Rust.
https://youtu.be/uCaYkUmdtPw (CODE GENIUS - What Other Languages Can Learn From Rust by Yehuda Katz)
One you have a decent understanding of the ownership system, you will be able to start writing functional Rust programs and work your way through other features.
- Cargo, the package manager for Rust is really good. You define your project details in a
Cargo.tomlfile and then runcargo build- Cargo will fetch all your dependencies and build your application.
Dependencies are versioned and the packages in the Cargo registry once published, can't be deleted, only hidden - your packages will always be able to fetch dependencies and compile.
[package]
name = "yourproject"
version = "1.2.0"
[[bin]]
name = "yourproject"
path = "src/bin/main.rs"
[dependencies]
log = "~0.3.4"
syntex_syntax = "~0.24.0"
toml = "~0.1.25"
In Rust you have to compile your applications/binaries on each machine where you want to use them. In C that would be something like:
./configure
make
make install
With Rust 1.5+ you can that with a simple cargo command: cargo install package. It will fetch/compile and store the resulting binary in the CARGO_HOME directory(which is $home/.cargo on Linux and Users/username/.cargo on Windows)
- Rust has very detailed and enjoyable to read documentation and a lot of resources to learn from.
I probably spent three weeks in total hacking in Rust and I did switch between Rust, Go and Python on a daily basis. This is what I can say about my experience with Rust:
-
Rust the language is stable, everything around it is not. Compared to more mature languages like Ruby, Python and even Go, you will not find many production ready 3rd party libraries - like a driver for a specific database for example.
-
the tooling is missing or work in progress. Compared to Go, which has go oracle, govet and gofmt and will show you errors as you type, the feedback loop in Rust is slow. You have to compile your application first. Compilation speed in Rust is far from ideal.
For me personally, Rust is an exciting new language. It does a lot of things right, it is just to early to use or to tell if it is going to have a bright future and replace C++. I personally will check Rust again next year.