如何将实现多个特征的结构传递给接受这些特征为&mut的函数?

How to pass a struct implementing multiple traits to a function accepting those traits as &mut?

提问人:PWolf 提问时间:10/13/2023 最后编辑:PWolf 更新时间:10/13/2023 访问量:60

问:

我正在处理一个问题,可以简化为以下代码。结果是 *dog。error[E0499]: cannot borrow as mutable more than once at a time.

错误的根源对我来说很清楚,但我想知道 - 解决它的正确方法是什么? 请注意,和 traits 使用一个公共字段来减少剩余的,所以对我来说,实现这两个字段是有意义的。WalkerSwimmerenergyDog

我想保持功能不变,因为步行和游泳的顺序对我来说很重要:)任何帮助将不胜感激。move_around

trait Swimmer {
    fn swim(&mut self);
}

trait Walker {
    fn walk(&mut self);
}

struct Fish {}

impl Swimmer for Fish {
    fn swim(&mut self) {}
}

struct Turtle {}

impl Walker for Turtle {
    fn walk(&mut self) {}
}

struct Dog {
    energy: usize,
}

impl Swimmer for Dog {
    fn swim(&mut self) {
        self.energy -= 1;
    }
}

impl Walker for Dog {
    fn walk(&mut self) {
        self.energy -= 1;
    }
}

fn move_around(walker: &mut dyn Walker, swimmer: &mut dyn Swimmer) {
    walker.walk();
    swimmer.swim();
}

fn main() {
    // This does not compile: error[E0499]: cannot borrow `*dog` as mutable more than once at a time
    let dog = &mut Dog { energy: 100 };
    move_around(dog, dog);

    // Works fine.
    // move_around(&mut Turtle {}, &mut Fish {});
}

我发现的一个可能的解决方案是拆分借用 - 这意味着将 和 字段添加到结构并将它们传递给函数,但共享状态使它变得复杂。WalkerSwimmerDog

编辑

我想在这种情况下,我可以使用我想避免的函数或添加一个新函数来接受具有特征边界的泛型:RefCell

fn move_around<T: Walker + Swimmer>(entity: &mut T) {
    entity.walk();
    entity.swim();
}
Rust 参考 特征 Borrow-Checker 可变

评论

0赞 drewtato 10/13/2023
使用特征对象而不是泛型是有原因的吗?如果是这样,您应该将其添加到问题中。
0赞 Chayim Friedman 10/13/2023
传递共享引用并使用内部可变性。
0赞 PWolf 10/13/2023
我添加了一个基于泛型的可能解决方案。这是你的意思@drewtato吗?
0赞 drewtato 10/13/2023
我真的在问原因。比如,你是从某个地方复制的,还是有人这样教你的?

答:

0赞 Chayim Friedman 10/13/2023 #1

您有以下几种选择:

  • 使用泛型(静态调度)并采用实现 和 的单个参数。WalkerSwimmer

  • 与前一个与动态调度的平行 - 创建一个同时具有 和 作为超级特征的新特征,为实现和的所有内容添加一个一揽子实现,然后接受。那是:WalkerSwimmerWalkerSwimmer&mut dyn WalkerAndSwimmer

    trait WalkerAndSwimmer: Walker + Swimmer {}
    impl<T: ?Sized + Walker + Swimmer> WalkerAndSwimmer for T {}
    
    fn move_around(v: &mut dyn WalkerAndSwimmer) {
        v.walk();
        v.swim();
    }
    

    这并不排除对步行者和游泳者使用不同类型的方法,只是需要更多的样板:定义一个结构,将它们作为字段和前向,并传递给它们,并传递它。WalkerSwimmer

  • 采用共享引用并使用内部可变性。有两种方法可以做到这一点。第一种是将特征的方法更改为共享,并将内部可变性(例如 RefCell)放在结构中。第二种是保持原样,但对游泳者来说也是如此。每种方法都有优点和缺点。&self&RefCell<dyn Walker>

    如果您既不能更改方法,也不能更改特征(例如,因为它们来自外部库),则可以保持原样,将 放入结构中,实现对 struct () 的共享引用的特征,并将可变引用传递给共享引用:。RefCellimpl Walker for &'_ Dog&mut &dog