Published on

Understanding Rust ref keyword

Authors

I'm currently building a native app at work with the Rust programming language (using tauri as my base framework) and I came across this match pattern where I did not want to consume the value bound to my variable. In Rust terms, I wanted to only get a reference to the inner value of a pattern.

This is where the ref keyword is handy. It allow us to extract the inner value of a pattern by borrowing instead of moving. Let's take a look at an example:

main.rs
fn main() {
  let greetings: Option<String> = Some(String::from("hi there"));

  // Here we consume the value bound to `greetings`,
  // no longer allowing it to be used across the scope.
  match greetings {
    Some(msg) => println!("{}", msg),
    None => println!("Nothing to greet"),
  }

  // When trying to use `greetings` from this point on...
  println!("Greetings? {}", greetings);
  // you will get the following compiler error:
  // |
  // |     Some(msg) => println!("{}", msg),
  // |          --- value partially moved here
  // |
  // |   println!("Greetings? {:?}", greetings);
  // |                               ^^^^^^^^^ value borrowed here after partial move
}

The compiler does a great job at warning you about that move, but it's an error, so it does not let you move forward. To fix that, you can use ref:

main.rs
fn main() {
  let greetings: Option<String> = Some(String::from("hi there"));

  match greetings {
    // ref now allows us to bind the inner content of the Option value
    // by borrowing instead of move
    Some(ref msg) => println!("{}", msg),
    None => println!("Nothing to greet"),
  }

  // This is now allowed. No compiler errors anymore.
  println!("Greetings? {:?}", greetings);
}

Binding behind a Mutex guard

One specific case I had was to borrow from a value that was protected by a Mutex and shared across threads. The ref keyword allowed me to borrow this shared value in a safe way.

main.rs
use std::sync::Mutex;

struct Settings {
    api_key: Option<String>,
}

fn main() {
    let value = Mutex::new(Settings {
        api_key: Some("some-key".into()),
    });

    // pretend this lock is being acquired on a different thread...
    let guard = value.lock().unwrap();

    if let Some(ref api_key) = guard.api_key {
        println!("got a handle on the api key: {}", api_key);
    }

    println!("guard still in scope and will be dropped: {:?}", guard.api_key);
}