Struggle with Rust compiler — 1 Link to heading

Struggle with Rust compiler — 1 Link to heading

Rust compiler is notorious for strict rules that it imposes on your code. As someone who is learning Rust, I have come across uncountable incidents where I had to fight the Rust compiler. I wanted to start a series of stories to compile case studies of various errors you may encounter. Hopefully, these series will serve as an invaluable database for anyone learning Rust.

Struggle with Rust compiler — unwrap & Option Link to heading

Rust compiler is notorious for strict rules that it imposes on your code. As someone who is learning Rust, I have come across uncountable incidents where I had to fight the Rust compiler. I wanted to start a series of stories to compile case studies of various errors you may encounter. Hopefully, these series will serve as an invaluable database for anyone learning Rust.

Here is the Compiler Explorer link to the first example. Consider a function below. It receives a borrowed optional string as an input, and it wants to print the string as is or empty if empty.

fn parse_option(opt: &Option<String>) {
    println!("{}", opt.unwrap_or("empty".to_owned()));
}

This simple one-liner function results in compile error

error[E0507]: cannot move out of `*opt` which is behind a shared reference
 --> <source>:2:20
  |
2 |     println!("{}", opt.unwrap_or("empty".to_owned()));
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because `*opt` has type `Option<String>`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
Compiler returned: 1

Let’s see what is going on. First, the parameter opt is of type &Option<String>, i.e., it is immutably borrowed Option, which means we can neither move it out or modify it. Now, let’s look at the method signature of Option::unwrap_or().

pub fn unwrap_or(self, default: T) -> T

The method clearly indicates self as the first parameter, meaning that it will move the object upon calling the method. That is exactly what the compiler is saying: cannot move out of *opt which is behind a shared reference.

Now that we know the root cause, let’s see how we can fix this. Ultimately, we need to access (read) the contained value, but can we do that without changing the function parameter type of opt? After all, this seems to be a fairly common scenario, so there must be a way to do it.

If you inspect documentation for Option, you will find a method Option::as_deref() with the following signature:

pub fn as_deref(&self) -> Option<<T as Deref>::Target>

The method essentially converts (without modifying/moving) Option<T> or &Option<T> into Option<&T>, which is exactly what we want. The first parameter to the method is &self, so we can call this method on our type &Option<T>. Technically, the method will output another Option instance that has a reference to the contained value, which we can now call unwrap_or() method. Hence, below is the fix:

--- before
+++ after
@@ -1,3 +1,3 @@
 fn parse_option(opt: &Option<String>) {
-    println!("{}", opt.unwrap_or("empty".to_owned()));
+    println!("{}", opt.as_deref().unwrap_or("empty"));
 }

First, we call as_deref() before calling unwrap_or() method. Second, we provide the alternative string empty as &str type rather than String type.

Hopefully this example helps your struggle with the Rust compiler. Don’t forget, you are not alone struggling to fight the compiler!

OK, you may raise a question. Both as_ref() and as_deref() look very similar, and whether as_ref() could work as well. The key difference is as_ref() is literal reference, where as_deref() is reference to Deref::Target. In our case, the contained value type of the option is String. In this case, we get

opt.as_ref(): Option<&String>
opt.as_deref(): Option<&str>

as String implements Deref trait with target of str. If we were to use as_ref(), we also need to make sure to provide &String type to unwrap_or() function, which can be doable but won’t be as concise as simply using as_deref().