我的网络应用程序的体系 struct 可以简化为以下几点:

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个对象发送了这个消息.

推荐答案

编译器正确地阻止你两次borrow HashMap.假设在handle_user_data()年你也试着借self.users.你会打破Rust借钱的规则,因为你已经有了可变借钱,而且你只能有一个.

既然你不能为你的handle_user_data()借两次self,我将提出一个解决方案.我不知道它是否是最好的,但它工作时没有不安全和开销(我认为).

我们的 idea 是使用一个中间 struct ,它将borrow 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次.

如果你只有一两件东西要借,一个快速的解决方案是有一个相关的函数,将它们作为参数:

impl UserHandler {
    fn handle_user_data(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;
    }
}

现在,我们确信我们没有开销,语法也更简洁.

更多信息可以在Niko Matsakis的博文After NLL: Interprocedural conflicts中找到.将此答案映射到博客帖子:

  • solution #1 -> "View structs as a general, but extreme solution" section
  • solution #2 -> "Free variables as a general, but extreme solution" section (here expressed as an associated function)
  • solution #3 -> "Factoring as a possible fix" section

Rust相关问答推荐

使用nom将任何空白、制表符、白线等序列替换为单个空白

在没有引用计数或互斥锁的情况下,可以从Rust回调函数内的封闭作用域访问变量吗?

如何导出 rust 色二进制文件中的符号

带扫描的铁 rust 使用滤镜

如何使用 list 在Rust for Windows中编译?

如何go 除铁 rust 中路径组件的第一项和最后一项?

为什么这个变量不需要是可变的?

循环访问枚举中的不同集合

为什么HashMap::get和HashMap::entry使用不同类型的密钥?

在为第三方 struct 实现第三方特征时避免包装器的任何方法

从字节数组转换为字节元组和字节数组时,为什么 Transmute 会对字节重新排序?

相当于 Rust 中 C++ 的 std::istringstream

如果不满足条件,如何在 Rust 中引发错误

切片不能被 `usize` 索引?

如何在 Rust 中将 UTF-8 十六进制值转换为 char?

如何在 use_effect_with_deps 中设置监听器内的状态?

预期的整数,找到 `&{integer}`

从 Cranelift 发出 ASM

为什么 match 语句对引用类型比函数参数更挑剔?

list 中没有指定目标 - 必须存在 src/lib.rs、src/main.rs、[lib] 部分或 [[bin]] 部分