【问题标题】:Manipulating an object from inside a loop that borrows it从借用它的循环内部操作对象
【发布时间】:2017-03-06 01:00:09
【问题描述】:

我正在用 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");
}

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

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    如你所见,你不能在借用self的一部分时调用&amp;mut self方法,所以你需要以某种方式重组。

    我这样做的方法是将process_message 所需的状态拆分为一个单独的类型(在您的示例中基本上是HashMap,但在实际应用程序中它可能包含更多),然后移动方法到那种类型。这是因为you can separately borrow fields from a struct

    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");
        }
    
    }
    

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

    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);
    }
    

    【讨论】:

    • 有趣。我第一次阅读你的第一个建议是我会得到同样的错误,因为在 self.state 上获取一个 mut ref 来调用 process_message 也会尝试获得一个 self 的 mut ref,但是你的例子确实有效......我'不知道为什么!我想这意味着我应该多读一些关于借贷的书
    • 可以单独借用struct的字段;我找到的第一个文档是doc.rust-lang.org/nomicon/borrow-splitting.html
    【解决方案2】:

    鉴于您不需要完整的ServerReader 来处理消息,您可以将process_message 设为一个免费函数并将&amp;mut self.counters 传递给它。然后你有 servercounters 的不相交借用,这很好。

    或者,如果ServerReader 的非server 部分更大,请将其提取到自己的结构中,并使process_message 成为该结构的 impl 方法。

    【讨论】:

      【解决方案3】:

      为了允许Iterator 中的可变性,您应该使用iter_mut() 并处理可变引用(&amp;mut message)。然后,为了避免额外的借用,您可以在循环体中执行加法:

      for &mut message in self.server.iter_mut() {
          println!("Received {}", message);
          *self.counters.entry(message).or_insert(0) += 1;
      }
      

      【讨论】:

      • 谢谢,但这并不是我真正想要的;这是一个非常简单的例子,我不喜欢内联做所有事情(这会以一个非常长的循环结束:)
      猜你喜欢
      • 2018-03-10
      • 1970-01-01
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-12
      相关资源
      最近更新 更多