从所有者对象读取时可变自身 [英] Mutable self while reading from owner object

查看:21
本文介绍了从所有者对象读取时可变自身的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个对象拥有另一个对象.拥有的对象有一个变异方法,该方法依赖于其所有者的非变异方法.架构(尽可能简化)如下所示:

I have one object that owns another. The owned object has a mutating method that depends on non-mutating methods of its owner. The architecture (simplified as much as possible) looks like this:

struct World {
    animals: Vec<Animal>,
}

impl World {
    fn feed_all(&mut self) {
        for i in 0..self.animals.len() {
            self.animals[i].feed(self);
        }
    }
}

struct Animal {
    food: f32,
}

impl Animal {
    fn inc_food(&mut self) {
        self.food += 1.0;
    }

    fn feed(&mut self, world: &World) {
        // Imagine this is a much more complex calculation, involving many
        // queries to world.animals, several loops, and a bunch of if
        // statements. In other words, something so complex it can't just
        // be moved outside feed() and pass its result in as a pre-computed value.
        for other_animal in world.animals.iter() {
            self.food += 10.0 / (other_animal.food + self.food);
        }
    }
}

fn main() {
    let mut world = World {
        animals: Vec::with_capacity(1),
    };

    world.animals.push(Animal { food: 0.0 });

    world.feed_all();
}

以上不编译.编译器说:

The above does not compile. The compiler says:

error[E0502]: cannot borrow `*self` as immutable because `self.animals` is also borrowed as mutable
 --> src/main.rs:8:34
  |
8 |             self.animals[i].feed(self);
  |             ------------         ^^^^- mutable borrow ends here
  |             |                    |
  |             |                    immutable borrow occurs here
  |             mutable borrow occurs here

我明白为什么会出现这个错误,但是 Rust 惯用的方法是什么?

I understand why that error occurs, but what is the idiomatic Rust way to do this?

为了清楚起见,示例代码不是真实的.它旨在尽可能简单地呈现核心问题.我正在编写的实际应用程序要复杂得多,而且与动物和喂养无关.

Just to be clear, the example code is not real. It's meant to present the core problem as simply as possible. The real application I'm writing is much more complex and has nothing to do with animals and feeding.

假设在调用 feed() 之前预先计算食物值是不切实际的.在实际应用中,类似于 feed() 的方法对 World 对象进行了多次调用,并对结果进行了大量复杂的逻辑处理.

Assume it is not practical to pre-compute the food value before the call to feed(). In the real app, the method that's analogous to feed() makes many calls to the World object and does a lot of complex logic with the results.

推荐答案

总而言之:Rust 拒绝编译我写的东西,做正确的事.在编译时无法知道我不会使我正在使用的数据无效.如果我得到一个指向一种动物的可变指针,编译器就不会知道我对该向量的只读访问权限不会因我对该特定动物的突变而失效.

In summary: Rust is doing the right thing by refusing to compile what I wrote. There's no way to know at compile time that I won't invalidate the data I'm using. If I get a mutable pointer to one animal, the compiler can't know that my read-only access to the vector isn't invalidated by my mutations to that particular animal.

因为这在编译时无法确定,所以我们需要某种运行时检查,或者我们需要使用不安全的操作来完全绕过安全检查.

Because this can't be determined at compile time, we need some kind of runtime check, or we need to use unsafe operations to bypass the safety checks altogether.

RefCell 是我们想要以运行时检查为代价的安全性的方法.UnsafeCell 至少是一种无需开销即可解决此问题的选项,当然是以安全为代价的.

RefCell is the way to go if we want safety at the cost of runtime checks. UnsafeCell is at least one option to solve this without the overhead, at the cost of safety of course.

我的结论是 RefCell 在大多数情况下更可取.开销应该是最小的.如果我们在获取值后对这些值做一些稍微复杂的事情,则尤其如此:有用操作的成本将使 RefCell 检查的成本相形见绌.虽然 UnsafeCell 可能会快一点,但它会导致我们犯错误.

I've concluded that RefCell is preferable in most cases. The overhead should be minimal. That's especially true if we're doing anything even moderately complex with the values once we obtain them: The cost of the useful operations will dwarf the cost of RefCell's checks. While UnsafeCell might be a little faster, it invites us to make mistakes.

下面是使用RefCell解决此类问题的示例程序.我选择了玩家、墙壁和碰撞检测,而不是动物和喂食.不一样的风景,一样的想法.该解决方案可推广到游戏编程中的许多非常常见的问题.例如:

Below is an example program solving this class of problem with RefCell. Instead of animals and feeding, I chose players, walls, and collision detection. Different scenery, same idea. This solution is generalizable to a lot of very common problems in game programming. For example:

  • 由 2D 图块组成的地图,其中每个图块的渲染状态取决于其邻居.例如.水边的草需要渲染海岸纹理.给定图块的渲染状态会在该图块或其任何相邻图块发生变化时更新.

  • A map composed of 2D tiles, where the render state of each tile depends on its neighbors. E.g. grass next to water needs to render a coast texture. The render state of a given tile updates when that tile or any of its neighbors changes.

如果 AI 的任何盟友与玩家交战,AI 就会向玩家宣战.

An AI declares war against the player if any of the AI's allies are at war with the player.

一块地形正在计算它的顶点法线,它需要知道相邻块的顶点位置.

A chunk of terrain is calculating its vertex normals, and it needs to know the vertex positions of the neighboring chunks.

无论如何,这是我的示例代码:

Anyway, here's my example code:

use std::cell::RefCell;

struct Vector2 {x: f32, y: f32}

impl Vector2 {
    fn add(&self, other: &Vector2) -> Vector2 {
        Vector2 {x: self.x + other.x, y: self.y + other.y}
    }
}

struct World {
    players: Vec<RefCell<Player>>,
    walls: Vec<Wall>
}

struct Wall;

impl Wall {
    fn intersects_line_segment(&self, start: &Vector2, stop: &Vector2) -> bool {
        // Pretend this actually does a computation.
        false
    }
}

struct Player {position: Vector2, velocity: Vector2}

impl Player {
    fn collides_with_anything(&self, world: &World, start: &Vector2, stop: &Vector2) -> bool {
        for wall in world.walls.iter() {
            if wall.intersects_line_segment(start, stop) {
                return true;
            }
        }

        for cell in world.players.iter() {
            match cell.try_borrow_mut() {
                Some(player) => {
                  if player.intersects_line_segment(start, stop) {
                      return true;
                  }
                },
                // We don't want to collision detect against this player. Nor can we,
                // because we've already mutably borrowed this player. So its RefCell
                // will return None.
                None => {}
            }
        }

        false
    }

    fn intersects_line_segment(&self, start: &Vector2, stop: &Vector2) -> bool {
        // Pretend this actually does a computation.
        false
    }

    fn update_position(&mut self, world: &World) {
        let new_position = self.position.add(&self.velocity);
        if !Player::collides_with_anything(self, world, &self.position, &new_position) {
            self.position = new_position;
        }
    }
}

fn main() {
    let world = World {
        players: vec!(
            RefCell::new(
              Player {
                  position: Vector2 { x: 0.0, y: 0.0},
                  velocity: Vector2 { x: 1.0, y: 1.0}
              }
            ),
            RefCell::new(
              Player {
                  position: Vector2 { x: 1.1, y: 1.0},
                  velocity: Vector2 { x: 0.0, y: 0.0}
              }
            )
        ),

        walls: vec!(Wall, Wall)
    };

    for cell in world.players.iter() {
        let player = &mut cell.borrow_mut();
        player.update_position(&world);
    }
}

上述内容可以修改为使用 UnsafeCell,只需很少的改动.但同样,我认为 RefCell 在这种情况下和大多数其他情况下更可取.

The above could be altered to use UnsafeCell with very few changes. But again,I think RefCell is preferable in this case and in most others.

感谢@DK 让我走上正确的道路来解决这个问题.

Thanks to @DK for putting me on the right track to this solution.

这篇关于从所有者对象读取时可变自身的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆