Вызов метода &mut self после доступа к свойству в Rust

Мне нужно сопоставить необязательное значение, которое находится в self, из метода, и на основе этого совпадения вызвать метод в self, который принимает self изменяемым образом. Я пытаюсь сделать довольно высокопроизводительную игру, поэтому, как бы я ни хотел обойтись без изменчивости, я не могу в этой ситуации: методы действительно нуждаются в изменяемом доступе к структуре, и им действительно нужно для отправки на основе свойств структур. Вот MVCE:

enum Baz {
    A,
    B,
    C,
}
struct Foo {
    bar: Option<Baz>,
}

impl Foo {
    pub fn dostuff(&mut self, fizz: i32) {
        if let Some(ref b) = self.bar {
            match b {
                &Baz::A => self.do_a(fizz),
                &Baz::B => self.do_b(fizz + 2),
                &Baz::C => self.do_c(fizz + 1),
            }
        }
    }

    pub fn do_a(&mut self, f: i32) {
        println!("A, with fizz {}", f);
    }
    pub fn do_b(&mut self, f: i32) {
        println!("B, with fizz {}", f);
    }
    pub fn do_c(&mut self, f: i32) {
        println!("C, with fizz {}", f);
    }
}

fn main() {
    let foo = Foo { bar: Some(Baz::A) };
    foo.dostuff(3);
}

А вот и игровая площадка.

error[E0502]: cannot borrow `*self` as mutable because `self.bar.0` is also borrowed as immutable
  --> src/main.rs:14:28
   |
12 |         if let Some(ref b) = self.bar {
   |                     ----- immutable borrow occurs here
13 |             match b {
14 |                 &Baz::A => self.do_a(fizz),
   |                            ^^^^ mutable borrow occurs here
...
18 |         }
   |         - immutable borrow ends here

Я думал, что примирился с проверкой заимствования, но, видимо, нет: я понятия не имею, как это исправить, хотя я знаю, почему это происходит. Буду признателен, если кто-нибудь объяснит, как избежать этого в будущем.


person Christopher Dumas    schedule 09.08.2017    source источник
comment
после доступа к свойству — не просто доступа, но и сохранения ссылки на него. Если бы do_a / do_b / do_c изменили self.b, это нарушило бы правила безопасности памяти Rust, потому что изменилась бы неизменяемая ссылка.   -  person Shepmaster    schedule 09.08.2017


Ответы (2)


Если вам важны ссылки, насколько я знаю, правила Rust запрещают вам иметь такой код. Вы должны либо:

  1. Copy или Clone ваш bar объект и разверните его так, как вы хотите.
  2. Добавьте какой-нибудь флаг и сделайте так, чтобы в коде не было множественных ссылок на объект. Например, добавьте две переменные, которые сообщат вам, что делать. Вы сопоставляете свое перечисление, устанавливаете эти переменные, а затем сопоставляете эти переменные. Это менее полезно, чем первый пункт здесь, но это тоже решение.

  3. Будьте более функциональны:

    enum Baz {
        A,
        B,
        C,
    }
    struct Foo {
        bar: Option<Baz>,
    }
    
    impl Foo {
        pub fn dostuff(&mut self, fizz: i32) {
            let (method, arg) = match self.bar {
                Some(ref b) => {
                    match b {
                        &Baz::A => (Self::do_a as fn(&mut Self, i32)>, fizz),
                        &Baz::B => (Self::do_b as fn(&mut Self, i32)>, fizz + 2),
                        &Baz::C => (Self::do_c as fn(&mut Self, i32)>, fizz + 1),
                    }
                },
                None => return,
            };
            method(self, arg);
        }
    
        pub fn do_a(&mut self, f: i32) {
            println!("A, with fizz {}", f);
        }
        pub fn do_b(&mut self, f: i32) {
            println!("B, with fizz {}", f);
        }
        pub fn do_c(&mut self, f: i32) {
            println!("C, with fizz {}", f);
        }
    }
    
    fn main() {
        let mut foo = Foo { bar: Some(Baz::A) };
        foo.dostuff(3);
    }
    

    Поступая таким образом, вы возвращаете метод, который вы хотите вызвать с его аргументом, поэтому вы просто вызываете то, что вам нужно. Я почти уверен, что это решение можно переписать без использования Box, а просто ссылаясь на метод, но я не знаю, как это сделать.

person Victor Polevoy    schedule 09.08.2017
comment
Я думаю, что я пойду с третьим вариантом! Я пытался клонировать вариант, но по какой-то причине это не помогло, и я также пытался сопоставить вариант, как вы сказали, и это не сработало, но, основываясь на том, что я знаю о правилах проверки заимствования, я думаю, что последний вариант будет работать . Спасибо! - person Christopher Dumas; 09.08.2017
comment
@ChristopherDumas Я отредактировал код, чтобы он больше не использовал Box, а использовал необработанные указатели на функции. - person Victor Polevoy; 21.08.2017

  1. Вы можете развернуть Option(Baz) внутри совпадения вместо использования if let..
  2. Объявить foo изменяемым

Вот код...

enum Baz {
    A,
    B,
    C,
}
struct Foo {
    bar: Option<Baz>,
}

impl Foo {
    pub fn dostuff(&mut self, fizz: i32) {
        match self.bar {
            Some(Baz::A) => self.do_a(fizz),
            Some(Baz::B) => self.do_b(fizz + 2),
            Some(Baz::C) => self.do_c(fizz + 1),
            None => {},
        }
    }

    pub fn do_a(&mut self, f: i32) {
        println!("A, with fizz {}", f);
    }
    pub fn do_b(&mut self, f: i32) {
        println!("B, with fizz {}", f);
    }
    pub fn do_c(&mut self, f: i32) {
        println!("C, with fizz {}", f);
    }
}

fn main() {
    let mut foo = Foo { bar: Some(Baz::B) };
    foo.dostuff(3);
}
person Malice    schedule 09.08.2017