For the past year (or more) I've been trying to decide on a new language to learn. Something that isn't frontend, something hot, something useful. I messed around with Go, I've had a weird fascination with C/C++ for as long as I can remember, and I dabbled in Haskell. Those are all great languages, but none of them stuck. I built a small webserver in Go that interacted with a Postgres DB, messed around with graphics programming in C++ and OpenGL, and ingested enough functional propaganda and monadic craziness from Haskell to get the gist of it. But again, nothing stuck. When I first started picking up Go I was constantly seeing these comparisons with Rust, on Reddit, which piqued my interest. 'Why is everyone waging a seeming religious war over these two languages?'. I gave in and started snooping around.

Yes. Rust is difficult. No. It isn't in competition with Go. Like any programming language, each serves its specific purpose. Each has its shortcomings, blah blah blah. What got me excited about Rust was its difficulty. I'm a glutton for punishment, what can I say. I've now been investing time in learning Rust for the past couple of months and thought I'd put some of these thoughts to pixel in a series of posts (hopefully, let's be real my track record for posting isn't too great).

Aside from the painstaking process of learning any language's standard library, the truly difficult part of upping your polyglot game is learning principles that one language has which another does not. Most people are familiar-enough with Javascript's schizophrenic nature to understand how wildly languages can vary and Rust is certainly no different. If Javascript can be personified as The Mad Hatter from Alice In Wonderland, Rust can be personified as a Buckingham Palace guard, predictable, a safe bet for timing, regimented, and -as a result- seemingly dry. To me, one of the most difficult points to grasp about the language is its strict adherence to who-owns-what value. Or, as Rust calls it, 'borrowing and ownership'. To illustrate what I mean:

fn main() {
let x = String::from("Owned");
let y = x;
println!("{}", x);
}


The above code won't be compiled and you'll get -aside from some linting warnings- a nasty error which complains that a "value was used after a move". More explicitly, the compiler gives you:

| error[E0382]: use of moved value: x
| --> main.rs:5:26
| |
|3| let y = x;
| |     - value moved here
|4| println!("{}", x);
| |                ^ valued used here after move
| |
| = note: move occurs because x has type std::string::String, which does not implement the Copy trait
|
| error: aborting due to previous error

Quick note: Look at how helpful the Rust compiler's output is! ^^

What happened was that x was initialized to a String with the value "Owned". We then initialized y with x. The Rust philosophy kicks in to high gear here. In most languages we'd end up getting a copy of the value that x was holding. In this case "Owned". But in Rust we have a longer-term goal in mind. Ideally, we'd be writing massive, and probably concurrent, programs with it (Rust). What we certainly don't want to do is have variables holding references to values that may or may not be nullified at some point. This is a massive, bite-you-in-the-ass move that you can do with C/C++. Rust only lets one variable point to a given value (read: point in memory) at a time and when one variable passes its value off to another variable, that original variable is then removed from memory. This guarantees that our programs won't have any memory leaks. Well, almost guarantees, but that's another story. Instead of copying x to y and keeping x usable the Rust compiler dereferences x and pulls it from memory (Stack or heap depending on how you initialized it. It's stack by default)

But what is the compiler telling us when it says "move occurs because x has type std::string::String, which does not implement the Copy trait"? Well, primitive values have a trait (simply put, a trait is a native method to a given type, sorta like .toString() in another language) called Copy which automatically copies the value of one variable to another so we don't see nasty compiler failures like above and we can create variable y from variable x and so on.

'Okay.', you say. 'That's cool. But what if I don't have a primtive value that I'm trying to pass around? What if I have a user-defined type called Person?' Well, I'll be diving into that in the future, but we can create structs and give it the Copy trait and we're set for passing values around willy-nilly.

The main takeaway from this small post is that Rust is difficult precisely because it attempts to be very safe with memory. Its aim is not to compete with Go, but to compete with systems-level languages like C/C++ in terms of memory safety and speed.

Stay tuned for more!

comment