Rust matching and borrow checker

2019-03-03 10:43发布

问题:

I keep stumbling on a pattern in my Rust programs that always puts me at odds with the borrow-checker. Consider the following toy example:

use std::sync::{Arc,RwLock};

pub struct Test {
    thing: i32,
}

pub struct Test2 {
    pub test: Arc<RwLock<Test>>,
    pub those: i32,
}

impl Test {
    pub fn foo(&self) -> Option<i32> {
        Some(3)
    }
}

impl Test2 {
    pub fn bar(&mut self) {
        let mut test_writer = self.test.write().unwrap();

        match test_writer.foo() {
            Some(thing) => {
                self.add(thing);
            },
            None => {}
        }
    }

    pub fn add(&mut self, addme: i32) {
        self.those += addme;
    }
}

This doesn't compile because the add function in the Some arm tries to borrow self mutably, which was already borrowed immutably just above the match statement in order to open the read-write lock.

I've encountered this pattern a few times in Rust, mainly when using RwLock. I've also found a workaround, namely by introducing a boolean before the match statement and then changing the value of the boolean in the Some arm and then finally introducing a test on this boolean after the match statement to do whatever it is I wanted to do in the Some arm.

It just seems to me that that's not the way to go about it, I assume there's a more idiomatic way to do this in Rust - or solve the problem in an entirely different way - but I can't find it. If I'm not mistaken the problem has to do with lexical borrowing so self cannot be mutably borrowed within the arms of the match statement.

Is there an idiomatic Rust way to solve this sort of problem?

回答1:

Use directly the field those, for example with custom type:

use std::sync::{Arc,RwLock};

pub struct Those(i32);

impl Those {
    fn get(&self) -> i32 {
        self.0
    }

    fn add(&mut self, n: i32) {
        self.0 += n;
    }
}

pub struct Test {
    thing: Those,
}

pub struct Test2 {
    pub test: Arc<RwLock<Test>>,
    pub those: Those,
}

impl Test {
    pub fn foo(&self) -> Option<Those> {
        Some(Those(3))
    }
}

impl Test2 {
    pub fn bar(&mut self) {
        let mut test_writer = self.test.write().unwrap();

        match test_writer.foo() {
            Some(thing) => {
                // call a method add directly on your type to get around the borrow checker
                self.those.add(thing.get());
            },
            None => {}
        }
    }
}


回答2:

You either need to end borrow of a part of self, before mutating self

pub fn bar1(&mut self) {
    let foo = self.test.write().unwrap().foo();
    match foo {
        Some(thing) => {
            self.add(thing);
        },
        None => {}
    }
}

or directly mutate non borrowed part of self

pub fn bar2(&mut self) {
    let test_writer = self.test.write().unwrap();

    match test_writer.foo() {
        Some(thing) => {
            self.those += thing;
        },
        None => {}
    }
}