简单重构触发借用检查器错误

时间:2019-07-13 09:12:54

标签: rust borrow-checker

我在Rust中编写的网络应用程序的体系结构可以简化为以下内容:

use std::collections::HashMap;

/// Represents remote user. Usually has fields,
/// but we omit them for the sake of example.
struct User;

impl User {
    /// Send data to remote user.
    fn send(&mut self, data: &str) {
        println!("Sending data to user: \"{}\"", data);
    }
}

/// A service that handles user data.
/// Usually has non-trivial internal state, but we omit it here.
struct UserHandler {
    users: HashMap<i32, User>,  // Maps user id to User objects.
    counter: i32  // Represents internal state
}

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            user.send("Message received!");
            self.counter += 1;
        }
    }
}

fn main() {
    // Initialize UserHandler:
    let mut users = HashMap::new();
    users.insert(1, User{});
    let mut handler = UserHandler{users, counter: 0};

    // Pretend we got message from network:
    let user_id = 1;
    let user_message = "Hello, world!";
    handler.handle_data(user_id, &user_message);
}

Playground。这样就可以了。然后,我想对其进行一些重构,即在UserHandler中创建一个单独的方法,以在我们已经确定存在具有给定id的用户时处理用户输入。这样就变成了:

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            self.handle_user_data(user, data);
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        self.counter += 1;
    }
}

Playground。突然之间,它无法编译!

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:24:13
   |
23 |         if let Some(user) = self.users.get_mut(&user_id) {
   |                             ---------- first mutable borrow occurs here
24 |             self.handle_user_data(user, data);
   |             ^^^^                  ---- first borrow later used here
   |             |
   |             second mutable borrow occurs here

乍看之下,错误非常明显:您无法对self和属性self进行可变引用-就像对self有两个可变引用一样。但是之后,我要做,在重构前代码中有两个像这样的可变引用!所以:

  1. 为什么这个简单的重构会触发借阅检查器错误?
  2. 如何解决它并分解UserHandler::handle_data这样的方法?

旁注:如果您想知道为什么要进行这种重构,请考虑一种情况,当用户可以发送多种消息时,所有消息都需要以不同的方式处理。但是有一个共同的部分:必须知道哪个User对象发送了此消息。

2 个答案:

答案 0 :(得分:7)

不幸的是,编译器是正确的,它阻止了您两次借入HashMap,假设您在handle_user_data()中也尝试借入self.users。您将破坏Rust中的借用规则,因为您已经在其上进行了变异借用,并且只能拥有一个借用。

因此,您不能为自己的self借用handle_user_data()两次,我将向您提出一个解决方案,我不知道这是否是最好的解决方案,但是它的工作不会带来安全隐患和开销(我认为)。因此,我们的想法是使用一个中间结构,该结构将借用self的另一个字段:

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            Middle::new(&mut self.counter).handle_user_data(user, data);
        }
    }
}

struct Middle<'a> {
    counter: &'a mut i32,
}

impl<'a> Middle<'a> {
    fn new(counter: &'a mut i32) -> Self {
        Self {
            counter
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        *self.counter += 1;
    }
}

通过这种方式,编译器知道我们不能借两次users。如果您只想借一两件事,一种快速的方法是在UserHandler实现中拥有一个将其作为参数的函数:fn handle_user_date(user: &mut User, data: &str, counter: &mut i32)

此外,我们可以通过以下操作轻松改进此设计:

struct UserHandler {
    users: HashMap<i32, User>, // Maps user id to User objects.
    middle: Middle,              // Represents internal state
}

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            self.middle.handle_user_data(user, data);
        }
    }
}

struct Middle {
    counter: i32,
}

impl Middle {
    fn new(counter: i32) -> Self {
        Self {
            counter
        }
    }

    fn handle_user_data(&mut self, user: &mut User, data: &str) {
        user.send("Message received!");
        self.counter += 1;
    }
}

现在我们确定我们没有开销,并且语法更简洁。

答案 1 :(得分:2)

调用self.handle_user_data时,您将整个self可变地使用,同时仍然具有可变的借用user对象,而借用检查器不喜欢这种对象。您不能同时拥有两个可变借款。

一种避免这种情况的方法不是可变地使用整个self而是可变地使用counter

impl UserHandler {
    fn handle_data(&mut self, user_id: i32, data: &str) {
        if let Some(user) = self.users.get_mut(&user_id) {
            handle_user_data(user, data, &mut self.counter);
        }
    }
}

fn handle_user_data(user: &mut User, data: &str, counter: &mut i32) {
    user.send(data);
    *counter += 1;
}