在惯用 Rust 中实现容器元素关系的正确方法

Correct way to implement container-element relationship in idiomatic Rust

我知道为什么 Rust 不喜欢我的代码。但是,我不知道什么是惯用的 Rust 方法来解决这个问题。

我是一名 C# 程序员,虽然我觉得我理解 Rust 的系统,但我认为我的 "old" 解决某些问题的方法在 Rust 中根本不起作用。

这段代码重现了我遇到的问题,它可能看起来不像惯用的 Rust(或者它甚至在 C# 中看起来也不太好):

//a "global" container for the elements and some extra data
struct Container {
    elements: Vec<Element>,
    global_contextual_data: i32,
    //... more contextual data fields
}

impl Container {
   //this just calculates whatever I need based on the contextual data
   fn calculate_contextual_data(&self) -> i32 {
       //This function will end up using the elements vector and the other fields as well, 
       //and will do some wacky maths with it. 
       //That's why I currently have the elements stored in the container
   }
}

struct Element {
    element_data: i32,
    //other fields
}

impl Element {
    //I need to take a mutable reference to update element_data, 
    //and a reference to the container to calculate something that needs 
    //this global contextual data... including the other elements, as previously stated
    fn update_element_data(&mut self, some_data: i32, container: &Container) {
        self.element_data *= some_data + container.calculate_contextual_data() //do whatever maths I need
    }
}


fn main(){

    //let it be mutable so I can assign the elements later
    let mut container = Container {
        elements: vec![],
        global_contextual_data: 1
    };

    //build a vector of elements
    let elements = vec![
        Element {
            element_data: 5
        },
        Element {
            element_data: 7
        }
    ];

    //this works
    container.elements = elements;

    //and this works, but container is now borrowed as mutable
    for elem in container.elements.iter_mut() {
        elem.element_data += 1; //and while this works
        let some_data = 2;

        //i can't borrow it as immutable here and pass to the other function
        elem.update_element_data(some_data, &container); 
    }
}

我明白为什么 elem.update_element_data(some_data, &container); 不起作用:我在调用 iter_mut 时已经将其作为可变借用了。也许每个元素都应该引用容器?但是这样我不是有更多的机会打破借贷检查吗?

我认为不可能将我的旧方法应用到这个新系统中。也许我需要重写整个事情。有人能指出我正确的方向吗?我刚开始用 Rust 编程,虽然所有权系统对我来说有点意义,但我应该写的代码 "around" 仍然不是那么清楚。

我遇到了这个问题: What's the Rust way to modify a structure within nested loops? 这让我深入了解了我的问题。

我重新审视了这个问题,并将问题归结为通过同时为写入和读取借用来共享向量。这是 Rust 所禁止的。我不想使用 unsafe 来规避借用检查器。不过,我想知道我应该复制多少数据?

我的 Element,它实际上是一个游戏的实体(我正在模拟一个点击游戏)同时具有可变和不可变的属性,我将它们分开了。

struct Entity {
    type: EntityType,
    starting_price: f64, 
    ...
    ...
    status: Cell<EntityStatus>
}

每次我需要更改实体的状态时,我都需要在 status 字段上调用 ​​getset 方法。 EntityStatus 导出 Clone, Copy.

我什至可以将字段直接放在结构上并让它们都是 Cells 但是使用它们会很麻烦(很多调用 getset), 所以我选择了更美观的方法。

通过允许自己复制 status、编辑和 set 它,我可以不可变地借用数组两次(.iter() 而不是 .iter_mut())。

我怕copy的时候性能不好,但实际上用opt-level=3编译后就很不错了。如果出现问题,我可能会将字段更改为 Cells 或想出另一种方法。

就在外面做计算:


#[derive(Debug)]
struct Container {
    elements: Vec<Element>
}

impl Container {
    fn compute(&self) -> i32 {
        return 42;
    }
    fn len(&self) -> usize {
        return self.elements.len();
    }
    fn at_mut(&mut self, index: usize) -> &mut Element {
        return &mut self.elements[index];
    }
}

#[derive(Debug)]
struct Element {
    data: i32
}

impl Element {
    fn update(&mut self, data: i32, computed_data: i32) {
        self.data *= data + computed_data;
    }
}

fn main() {
    let mut container = Container {
        elements: vec![Element {data: 1}, Element {data: 3}]
    };
    
    println!("{:?}", container);
    
    for i in 0..container.len() {
        let computed_data = container.compute();
        container.at_mut(i).update(2, computed_data);
    }

    println!("{:?}", container);
}

另一种选择是将 update_element 添加到您的容器中:


#[derive(Debug)]
struct Container {
    elements: Vec<Element>
}

impl Container {
    fn compute(&self) -> i32 {
        let sum = self.elements.iter().map(|e| {e.data}).reduce(|a, b| {a + b});
        return sum.unwrap_or(0);
    }
    fn len(&self) -> usize {
        return self.elements.len();
    }
    fn at_mut(&mut self, index: usize) -> &mut Element {
        return &mut self.elements[index];
    }
    fn update_element(&mut self, index: usize, data: i32) {
        let computed_data = self.compute();
        self.at_mut(index).update(data, computed_data);
    }
}

#[derive(Debug)]
struct Element {
    data: i32
}

impl Element {
    fn update(&mut self, data: i32, computed_data: i32) {
        self.data *= data + computed_data;
    }
}

fn main() {
    let mut container = Container {
        elements: vec![Element {data: 1}, Element {data: 3}]
    };
    
    println!("{:?}", container);
    
    for i in 0..container.len() {
        let computed_data = container.compute();
        container.at_mut(i).update(2, computed_data);
    }

    println!("{:?}", container);
    
    for i in 0..container.len() {
        container.update_element(i, 2);
    }

    println!("{:?}", container);
}

Try it!