从一个借用它的循环内部操作一个对象

Sht*_*ong 22 rust borrow-checker

我在Rust中编写了一些连接到远程服务器的代码,根据该服务器发送的消息,计算一些统计信息或根据这些统计信息执行操作.但这对我来说更像是一个学习项目,而且我遇到了一个问题.

这是我已经减少到最低限度以重现问题的代码:

// Repro code for error[E0502]: cannot borrow `*self` as mutable because `self.server` is also borrowed as immutable

use std::collections::HashMap;

struct ServerReader {
    server: Vec<u32>, // A vec for demo purposes, but please imagine this is a server object
    counters: HashMap<u32, usize>,
}

impl ServerReader {
    fn new() -> ServerReader {
        ServerReader {
            server: vec!(1, 2, 5, 2, 7, 9, 1, 1, 5, 6), // Filling my "server" with some messages
            counters: HashMap::new(),
        }
    }

    fn run(&mut self) {
        println!("Connecting..."); // ... here there should be some code to connect to the server ...

        for message in self.server.iter() { // We wait for the network messages sent by the server, and process them as they come
//                     ----------- immutable borrow occurs here
            println!("Received {}", message);
            self.process_message(*message); // HOW
//          ^^^^ mutable borrow occurs here
        }
//      - immutable borrow ends here
        println!("Disconnected");
    }

    fn process_message(&mut self, message: u32) {
        // Please imagine that this function contains complex stuff
        let counter = self.counters.entry(message).or_insert(0);
        *counter += 1;
    }
}

fn main() {
    let mut reader = ServerReader::new();

    reader.run();

    println!("Done");
}
Run Code Online (Sandbox Code Playgroud)

虽然我认为我理解为什么编译器不满意,但我很难想出一个解决方案.我不能在循环之外操纵我的结构,因为我必须在连接和监听服务器时工作.我也可以将所有内容直接放在循环中而不调用任何方法,但我不想最终得到1000行循环(我更愿意理解实际解决方案的样子).

Chr*_*son 16

正如你已经解决的那样,&mut self当你借用一部分时,你不能称之为方法self,所以你需要以某种方式进行重组.

我这样做的方法是将所需的状态拆分process_message为一个单独的类型(在你的示例中基本上是HashMap,但在实际应用程序中它可能包含更多),并将方法移动到该类型.这是有效的,因为您可以单独从结构中借用字段.

struct SomeState {
    counters: HashMap<u32, usize>,
}

impl SomeState {
    pub fn new() -> SomeState {
        SomeState {
            counters: HashMap::new(),
        }
    }
    fn process_message(&mut self, message: u32) {
        let counter = self.counters.entry(message).or_insert(0);
        *counter += 1;
    }
}

struct ServerReader {
    server: Vec<u32>,
    state: SomeState,
}

impl ServerReader {
    fn new() -> ServerReader {
        ServerReader {
            server: vec!(1, 2, 5, 2, 7, 9, 1, 1, 5, 6),
            state: SomeState::new(),
        }
    }

    fn run(&mut self) {
        println!("Connecting...");

        for message in self.server.iter() {
            println!("Received {}", message);
            self.state.process_message(*message);
        }
        println!("Disconnected");
    }

}
Run Code Online (Sandbox Code Playgroud)

另一种选择(在你的真实例子中可能或不可能)是避免在循环中借用,使其更像:

loop {
    // if next_message() returns an owned message, ie not still borrowing
    // self
    let message = self.next_message();
    // now no borrow left
    self.process_message(message);
}
Run Code Online (Sandbox Code Playgroud)

  • 你可以单独借用struct的字段; 我找到的第一篇文档是https://doc.rust-lang.org/nomicon/borrow-splitting.html (2认同)

Seb*_*edl 5

鉴于您不需要完整ServerReader处理消息,您可以创建process_message一个自由函数并传递&mut self.counters给它.然后你有不相交的借阅servercounters,这是罚款.

或者,如果您的非server部分ServerReader更大,则将其提取到其自己的结构中,并创建process_message该结构的impl方法.