Подтвердить что ты не робот

Манипулирование объекта изнутри цикла, который заимствует его

Я пишу код в 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-строчным циклом (и я бы предпочел понять, как будет выглядеть фактическое решение).

4b9b3361

Ответ 1

Как вы уже выработали, вы не можете вызвать метод &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");
    }

}

Альтернативой (которая может быть или может быть невозможна в вашем реальном примере) было бы избежать заимствования в цикле, сделав его более похожим:

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

Ответ 2

Учитывая, что для обработки сообщения вам не нужен полный ServerReader, вы можете сделать process_message бесплатную функцию и просто передать &mut self.counters. Тогда у вас есть непересекающиеся записи server и counters, что хорошо.

Или, если ваша часть server ServerReader больше, извлеките ее в свою собственную структуру и сделайте process_message метод impl этой структуры.

Ответ 3

Чтобы разрешить изменчивость в Iterator, вы должны использовать iter_mut() и работать с изменяемыми ссылками (&mut message). Затем, чтобы избежать дополнительного заимствования, вы можете просто выполнить добавление в тело цикла:

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