Rust 在迭代另一个不可变字段时对一个字段进行变异

Rust 在迭代另一个不可变字段时对一个字段进行变异,rust,Rust,鉴于以下计划: struct Data { pub items: Vec<&'static str>, } trait Generator { fn append(&mut self, s: &str) { self.output().push_str(s); } fn data(&self) -> &Data; fn generate_items(&mut self)

鉴于以下计划:

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&mut self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&self) -> &Data;

    fn generate_items(&mut self) {
        for item in self.data().items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
    }

    fn output(&mut self) -> &mut String;
}

struct MyGenerator<'a> {
    data: &'a Data,
    output: String,
}

impl<'a> MyGenerator<'a> {
    fn generate(mut self) -> String {
        self.generate_items();

        self.output
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&self) -> &Data {
        self.data
    }

    fn output(&mut self) -> &mut String {
        &mut self.output
    }
}

fn main() {
    let data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &data,
        output: String::new(),
    };

    let output = generator.generate();

    println!("{}", output);
}
构造代码的正确方法是什么,以便在不可变字段
数据
上迭代时,可以写入可变字段
输出
?假设通过
生成器
trait的间接寻址用于与其他结构共享类似的逻辑,因此从trait的默认方法实现中访问
MyStruct
的字段需要通过如下访问器方法完成

假设通过
生成器
trait的间接寻址用于与其他结构共享类似的逻辑,因此从trait的默认方法实现中访问
MyStruct
的字段需要通过如下访问器方法完成

那就不可能了


当编译器直接看到不同的字段时,它会识别对这些字段的访问;它不会打破抽象边界来窥视调用的函数内部

已经讨论过在方法上添加属性,以明确指出哪个字段由哪个方法访问:

  • 编译器将强制方法不接触属性中未提及的任何字段
  • 然后,编译器可以使用所述方法仅对字段子集进行操作的知识
然而。。。这适用于非虚拟方法

对于一个trait,这会变得非常复杂,因为trait没有字段,每个实现者可能有一组不同的字段


那现在怎么办

您需要更改代码:

  • 您可以将特征一分为二,并需要两个对象(一个用于迭代,一个用于变异)
  • 您可以“隐藏”append方法的可变性,强制用户使用内部可变性

这是锈病中的常见问题;解决这一问题的典型方法是替代舞蹈。这涉及到使更多的数据和方法使用可变引用:

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&mut self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&mut self) -> &mut Data;

    fn generate_items(&mut self) {
        // Take the data. The borrow on self ends after this statement.
        let data = std::mem::replace(self.data(), Data { items: vec![] });
        // Iterate over the local version. Now append can borrow all it wants.
        for item in data.items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
        // Put the data back where it belongs.
        std::mem::replace(self.data(), data);
    }
    fn output(&mut self) -> &mut String;
}

struct MyGenerator<'a> {
    data: &'a mut Data,
    output: String,
}

impl<'a> MyGenerator<'a> {
    fn generate(mut self) -> String {
        self.generate_items();

        self.output
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&mut self) -> &mut Data {
        self.data
    }

    fn output(&mut self) -> &mut String {
        &mut self.output
    }
}

fn main() {
    let mut data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &mut data,
        output: String::new(),
    };

    let output = generator.generate();

    println!("{}", output);
}
struct数据{
发布项目:Vec{
数据:&'mut数据,
输出:字符串,
}
恳求{
fn生成(mut self)->字符串{
self.generate_items();
自我输出
}
}
恳求{
fn数据(&mut self)->&mut数据{
自我数据
}
fn输出(&mut self)->&mut字符串{
&多自输出
}
}
fn main(){
让mut data=data{
物品:vec![“foo”,“bar”,“baz”],
};
让生成器=MyGenerator{
数据:&mut数据,
输出:String::new(),
};
让output=generator.generate();
println!(“{}”,输出);
}
需要认识到的是,编译器抱怨是对的。想象一下,如果调用
output()
会产生副作用,使
data()的返回值引用的内容发生变化
然后,循环中使用的迭代器可能会失效。您的trait函数有一个隐式约定,即它们不会执行类似的操作,但无法检查这一点。因此,您唯一能做的就是暂时假定完全控制数据,将其取出

当然,这种模式破坏了放松安全;循环中的恐慌将使数据移出。

您可以使用:

RefCell使用Rust的生命周期实现“动态借用”,这是一种 一个人可以声称临时的、独占的、可变的访问权限的过程 “在运行时”跟踪RefCells的内部值, 与Rust完全跟踪的本机引用类型不同 静态地,在编译时。因为RefCell借用是动态的,所以 可以尝试借用已经可变的值 借用;发生这种情况时会导致线程恐慌

使用std::cell::{RefCell,RefMut};
结构数据{
发布项目:Vec{
数据:&'a数据,
输出:RefCell,
}
恳求{
fn生成(自)->字符串{
self.generate_items();
self.output.into_inner()
}
}
恳求{
fn数据(&self)->&data{
自我数据
}
fn输出(&self)->RefMut{
self.output.borrow_mut()
}
}
fn main(){
让数据=数据{
物品:vec![“foo”,“bar”,“baz”],
};
让生成器=MyGenerator{
数据:&数据,
输出:RefCell::new(String::new()),
};
让output=generator.generate();
println!(“{}”,输出);
}

您个人是否喜欢如何处理?使用
RefCell
?将不可变数据和输出字符串拆分为两个单独的对象?去掉特征,只需在一种类型中处理所有内容?@JimmyCuadra:对于这个特定的示例,我会说两个特征和两个结构……但我假设这是一个简化的示例这就是为什么我提出了多种解决方案,并让您来选择最适合您的解决方案。一方面,这需要将
数据的定义更改为
fn数据(&mut self)->&mut数据
(请注意,到目前为止,它一直保持不变).另一方面,我觉得它相当优雅!对于以后遇到此问题的任何人,我相信此RFC解决了此问题中的问题:
use std::cell::{RefCell, RefMut};

struct Data {
    pub items: Vec<&'static str>,
}

trait Generator {
    fn append(&self, s: &str) {
        self.output().push_str(s);
    }

    fn data(&self) -> &Data;

    fn generate_items(&self) {
        for item in self.data().items.iter() {
            match *item {
                "foo" => self.append("it was foo\n"),
                _ => self.append("it was something else\n"),
            }
        }
    }

    fn output(&self) -> RefMut<String>;
}

struct MyGenerator<'a> {
    data: &'a Data,
    output: RefCell<String>,
}

impl<'a> MyGenerator<'a> {
    fn generate(self) -> String {
        self.generate_items();

        self.output.into_inner()
    }
}

impl<'a> Generator for MyGenerator<'a> {
    fn data(&self) -> &Data {
        self.data
    }

    fn output(&self) -> RefMut<String> {
        self.output.borrow_mut()
    }
}

fn main() {
    let data = Data {
        items: vec!["foo", "bar", "baz"],
    };

    let generator = MyGenerator {
        data: &data,
        output: RefCell::new(String::new()),
    };

    let output = generator.generate();

    println!("{}", output);
}