osa1 feed

More Rust woes

October 8, 2017 - Tagged as: en, rust.

In the third part of the series (1, 2) we’re going to look at two curious cases, first one related with “drop checker” and the second one with “borrow checker”.

(examples below are tested with rustc 1.22.0-nightly (05f8ddc46 2017-10-07))

1. Redundant semicolon fixes borrow (or drop) checker

It turns out if you’re getting a weird borrow checker error about something not living long enough to be dropped you can sometimes fix it by adding more semicolons.

The error message itself is weird because intuitively you’d think that for something to be dropped it should first become dead, but the error message says something like “x dropped here while still borrowed”. Because the variable is not dropped explicitly by the user, this error message is actually complaining about compiler’s behavior not being consistent in itself.

Here’s an example:

use std::sync::Arc;
use std::sync::Mutex;

fn main() {
    let s: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
    match s.lock().unwrap() {
        _ => {}
    }
}

This fails to compile with:

error[E0597]: `s` does not live long enough
 --> src/main.rs:9:1
  |
6 |     match s.lock().unwrap() {
  |           - borrow occurs here
...
9 | }
  | ^ `s` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

Solution? Add more semicolons! In this example, just put a semicolon after the match expression and it compiles fine.

Here’s one more example. It turns out questions about this error message are regularly asked on the IRC channel.

#21114 reported this issue on Jan 14, 2015, but no progress has been made towards a solution so far. It’s not clear if non-lexical lifetimes will help solving this.

2. match expression keeps values alive longer than necessary

This problem is kind of special. All other problems mentioned in this series were about borrow checker being too strict. This one is different: it causes runtime bugs.

match expression keeps the value to be examined (sometimes called scrutinee in Haskell land) alive longer than necessary. Because alive values are not dropped, if you rely on dynamic borrow checks in the scrutinee and in the branches, your checks fail. Here’s an example:

use std::sync::Arc;
use std::sync::Mutex;

struct S(i32);

impl S {
    pub fn get_int(&self) -> i32 {
        self.0
    }

    pub fn set_int(&mut self, i: i32) {
        self.0 = i;
    }
}

fn main() {
    let s = Arc::new(Mutex::new(S(0)));
    match s.lock().unwrap().get_int() {
        i => {
            s.lock().unwrap().set_int(i);
        }
    };
}

(notice how I use a redundant semicolon after the match expression, to fix #1)

Even though get_int() returns in i32 as a value, not a reference, the MutexGuard returned by Mutex::lock() is kept alive in the branches of this match expression, so the second Mutex::lock() call causes a deadlock. Solution? Use a let expression:

let x = s.lock().unwrap().get_x();
match x { ... }

This problem makes match <expr> { ... } less useful than let x = <expr>; match x { ... }.

#38355 reported this issue on Dec 14, 2016, but no progress towards a solution has been made so far.