【问题标题】:Passing mutable self reference to method of owned object将可变的自引用传递给拥有对象的方法
【发布时间】:2015-06-06 10:01:06
【问题描述】:

以下是一个简单的模拟,其场地是一个矩形区域,两个球在其中弹跳。 Field 结构有一个update 方法,它在每个球上调用update。球,在他们的update 方法中,需要根据它们的速度移动。但他们也需要相互反应,以及领域的界限。:

fn main() {
    let mut field = Field::new(Vector2d { x: 100, y: 100 });
    field.update();
}

#[derive(Copy, Clone)]
struct Vector2d {
    x: i32,
    y: i32,
}

struct Ball {
    radius: i32,
    position: Vector2d,
    velocity: Vector2d,
}

impl Ball {
    fn new(radius: i32, position: Vector2d, velocity: Vector2d) -> Ball {
        Ball {
            radius: radius,
            position: position,
            velocity: velocity,
        }
    }

    fn update(&mut self, field: &Field) {
        // check collisions with walls
        // and other objects
    }
}

struct Field {
    size: Vector2d,
    balls: [Ball; 2],
}

impl Field {
    fn new(size: Vector2d) -> Field {
        let position_1 = Vector2d {
            x: size.x / 3,
            y: size.y / 3,
        };
        let velocity_1 = Vector2d { x: 1, y: 1 };
        let position_2 = Vector2d {
            x: size.x * 2 / 3,
            y: size.y * 2 / 3,
        };
        let velocity_2 = Vector2d { x: -1, y: -1 };

        let ball_1 = Ball::new(1, position_1, velocity_1);
        let ball_2 = Ball::new(1, position_2, velocity_2);

        Field {
            size: size,
            balls: [ball_1, ball_2],
        }
    }

    fn update(&mut self) {
        // this does not compile
        self.balls[0].update(self);
        self.balls[1].update(self);
    }
}

如何将边界和另一个球的信息传递给Ball 结构的更新函数? Field::update 中的这些行无法编译:

self.balls[0].update(self);
self.balls[1].update(self);

给出以下错误:

error[E0502]: cannot borrow `*self` as immutable because `self.balls[..]` is also borrowed as mutable
  --> src/main.rs:62:30
   |
62 |         self.balls[0].update(self);
   |         -------------        ^^^^- mutable borrow ends here
   |         |                    |
   |         |                    immutable borrow occurs here
   |         mutable borrow occurs here

我明白,但我不知道如何解决这个问题。

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    目前您的Ball 结构需要了解它所包含的Field 才能进行自我更新。这不会编译,因为结果将是循环引用与突变相结合。您可以使用CellRefCell(后者具有性能成本)来完成这项工作,但以不同的方式构造代码会更好。让Field 结构检查并解决Ball-BallBall-Wall 冲突。 Ball 结构的update 函数可以处理更新Ball 的位置。

    // Ball's update function
    fn update(&mut self) {
        // update position
    }
    
    // Field's update function
    fn update(&mut self) {
        for ball in self.balls.iter_mut() {
            ball.update();
        }
    
        // check for collisions
    
        // resolve any collisions
    }
    

    【讨论】:

    • 是的,这行得通。伙计,Rust 真的让我对我的编程方式有了不同的看法。
    【解决方案2】:

    这是一个较小的例子:

    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &Field) {}
    }
    
    struct Field {
        ball: Ball,
    }
    
    impl Field {
        fn update(&mut self) {
            self.ball.update(self)
        }
    }
    

    问题

    当您传入对Field 的引用时,您保证Field 不能更改(“不可变引用”的不可变 部分)。然而,这段代码也试图改变它的一部分:球!在Ball::update的实现中,哪个引用应该是权威的,selffield

    解决方案:只使用您需要的字段

    您可以将update 所需的结构部分和不需要的部分分开并在调用update 函数之前使用它们

    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &u8) {}
    }
    
    struct Field {
        players: u8,
        ball: Ball,
    }
    
    impl Field {
        fn update(&mut self) {
            self.ball.update(&self.players)
        }
    }
    

    您甚至可以将这些零散的参考资料打包成一个整洁的包:

    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: BallUpdateInfo) {}
    }
    
    struct BallUpdateInfo<'a> {
        players: &'a u8,
    }
    
    struct Field {
        players: u8,
        ball: Ball,
    }
    
    impl Field {
        fn update(&mut self) {
            let info = BallUpdateInfo { players: &self.players };
            self.ball.update(info)
        }
    }
    

    或者重组你的包含结构以从头开始分离信息:

    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &UpdateInfo) {}
    }
    
    struct UpdateInfo {
        players: u8,
    }
    
    struct Field {
        update_info: UpdateInfo,
        ball: Ball,
    }
    
    impl Field {
        fn update(&mut self) {
            self.ball.update(&self.update_info)
        }
    }
    

    解决方案:从self中删除该成员

    您也可以采用 other 方式,在对其进行任何更改之前从 Field 中删除 Ball。如果您可以轻松/廉价地制作Ball,请尝试替换它:

    use std::mem;
    
    #[derive(Default)]
    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &Field) {}
    }
    
    struct Field {
        ball: Ball,
    }
    
    impl Field {
        fn update(&mut self) {
            let mut ball = mem::replace(&mut self.ball, Ball::default());
            ball.update(self);
            self.ball = ball;
        }
    }
    

    如果你不能轻易地创建一个新值,你可以使用Optiontake它:

    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &Field) {}
    }
    
    struct Field {
        ball: Option<Ball>,
    }
    
    impl Field {
        fn update(&mut self) {
            if let Some(mut ball) = self.ball.take() {
                ball.update(self);
                self.ball = Some(ball);
            }
        }
    }
    

    解决方案:运行时检查

    您可以通过 RefCell 将借用检查移至运行时而不是编译时:

    use std::cell::RefCell;
    
    struct Ball {
        size: u8,
    }
    
    impl Ball {
        fn update(&mut self, field: &Field) {}
    }
    
    struct Field {
        ball: RefCell<Ball>,
    }
    
    impl Field {
        fn update(&mut self) {
            self.ball.borrow_mut().update(self)
        }
    }
    

    【讨论】:

    • 这是一个非常广泛和好的答案@Shepmaster。从 Rust 开始最困难的事情之一可能就是学习这些模式。除了链表书之外,我希望在一个地方有这样的解决方案资源。它可以帮助你思考代码,以及如何在 Rust 的自然边界内塑造它。
    猜你喜欢
    • 2023-04-09
    • 2013-10-12
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-26
    • 1970-01-01
    • 2012-07-05
    相关资源
    最近更新 更多