Skip to main content




These are the things I picked up (over time) while learning Rust.


  • Mutability is a first class concept in Rust. Values by default can not be mutated. You’ll need to add the mut keyword for that.
let foo = 1 // immutable
let mut bar = 2 // mutable
  • References are by default also not mutable.
// Given mutable string
let mut s1 = String::from("foo");
// Example 1
fn side_effect(string: &String) {
println!("{}", string);

// pass s1 by (immutable) reference
// Example 2
fn function_that_updates_string(string: &mut String) {
// The * gives mutable access to the variables value.
*string = string.to_string();

// pass s1 by (mutable) reference
function_that_updates_string(&mut s1);


  • Each value has an owner (a value is tied to a variable), there is only one owner of a value (other variables can borrow). The value gets dropped when the owner goes out of scope.
fn move_value_to_new_pointer() {
let s1 = String::from("abc");
// -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait

let s2 = s1;
// -- value moved here (s1 no longer points to the value)

// println!("{}", s1);
// Error: ^^ value borrowed here after move

fn copy_value_to_new_pointer() {
let s1 = String::from("abc");
let s2 = s1.clone();

println!("{} and {}", s1, s2);
// Outputs: "abc and abc"


Closures, in some languages called anonymous functions, are very powerful in Rust. It’s important to know how they behave about ownership as well.

// Example closure
|x, y| x + y

// Left-hand side will automatically borrow a referene to values in the enclosing scope
|| x + y
// Any owned type
let s = String::from("🐈");

// Optionally clone to preserve the original
let s2 = s.clone();

// You can move values using the move keyword, so that they are owned by the closure.
let f = move || println!("{}", s2);

// Compilation error: value of s2 has moved to a different variable (that inside of the closure).
println!("{}", s2);

// Invoke the closure
f(); // prints "🐈"

Return signatures


// `Option` resolves into `Some` or `None`
fn get_status(username: &str) -> Option<&str> {
// some user lookup code here...
if(!user_exists) return None;
// if user exists, fetch their status and return that...

// now let's use that function
let result = get_status("");
match result {
Some(status) => println!("{}", status),
None => println!("couldn't find a status for"),


// `Result` resolves into `Ok` or `Err`
fn get_status(username: &str) -> Result<&str, String> {
// some user lookup code here...
if(!user_exists) return Err("couldn't find user!".to_string());
// if user exists, fetch their status and return that...

// now let's use that function
let result = get_status("");
match result {
Ok(status) => println!("{}", status),
Err(e) => println!("{}", e),


Use sparingly, as it can panic

// Unwrap is a shorthand...
let status = get_status("").unwrap();

// For `Option`
let status = match get_status("") {
Some(s) => s,
None => panic!()

// For `Result`
let status = match get_status("jakedawkins") {
Ok(s) => s,
Err(e) => panic!(e),


The way of abstraction in rust is really well thought through.

  1. Example: abstracting away error handling by implementing Display (notice the block impl Display for ProcessingError {}). This honestly blew my mind the first time I saw it and realized how powerful abstractions are in Rust.
  2. More on error handling: RustConf 2020 - Error handling Isn't All About Errors by Jane Lusby
  3. Abstracting away implementations on structs

What are your thoughts?