如何创建动态创造价值并回报借款的工厂?

How to create factory that dynamically creates values and returns borrows to them?

提问人:Kamil Szot 提问时间:12/22/2022 最后编辑:Kamil Szot 更新时间:12/22/2022 访问量:79

问:

我想要一个叫做 Factory 的工厂,它可以动态地生产新的,将它们保留在自己内部并返回借用的它们,这些借款的寿命与工厂本身的价值一样长。structStrings&str

我试图将新值保留在里面,但随着增长,对元素的借用会失效,因此它们的寿命不够长。我试着把它们包起来,但我遇到了同样的问题。VecVecBoxesRefCells

我还想在循环中调用这个工厂方法,这样我就可以在每次迭代中创建新的 String 并借用它以保存在某个地方。


有一个叫做string-interner的箱子:https://docs.rs/string-interner/latest/string_interner/index.html

如果您想要的只是 String 句柄,那么直接或通过类似的结构使用它可能是一个好主意,如下所示。


这就是我到目前为止所得到的,这要归功于您的评论:

use std::{ cell::{Ref, RefCell}, rc::Rc, }; 

struct StringHandle {
    key: usize,
    store: Rc<RefCell<Vec<String>>>,
}
impl StringHandle {
    pub fn get(&self) -> Ref<String> {
        Ref::map(self.store.borrow(), |v| &v[self.key])
    }
}

struct Factory {
    pub store: Rc<RefCell<Vec<String>>>,
}
impl Factory {
    pub fn make_next_string(&mut self) -> StringHandle {
        let len = self.store.borrow().len();
        self.store.borrow_mut().push(format!("string no. {}", len));
        StringHandle {
            store: self.store.clone(),
            key: len,
        }
    }
    pub fn new() -> Factory {
        Factory { store: Rc::new(RefCell::new(vec![])) }
    }
}

let mut f = Factory::new();
let mut strs: Vec<StringHandle> = vec![];

for _ in 0..5 {
    let handle = f.make_next_string();
    strs.push(handle);
}

for handle in strs {
    println!("{}", handle.get());
}

以及 String 以外的结构的通用版本:

use std::{ cell::{Ref, RefCell, RefMut}, rc::Rc, }; 

struct Handle<T> {
    key: usize,
    store: Rc<RefCell<Vec<T>>>,
}
impl<T> Handle<T> {
    pub fn get(&self) -> Ref<T> {
        Ref::map(self.store.borrow(), |v| &v[self.key])
    }
    pub fn get_mut(&self) -> RefMut<T> {
        RefMut::map(self.store.borrow_mut(), |v| &mut v[self.key])
    }
}

struct Factory<T> {
    pub store: Rc<RefCell<Vec<T>>>,
}
impl<T: Default> Factory<T> {
    pub fn make_next(&mut self) -> Handle<T> {
        let len = self.store.borrow().len();
        self.store.borrow_mut().push(T::default());
        Handle {
            store: self.store.clone(),
            key: len,
        }
    }
    pub fn new() -> Factory<T> {
        Factory { store: Rc::new(RefCell::new(vec![])) }
    }

}

#[derive(Debug)]
struct Data {
    pub number: i32
}

impl Default for Data {
    fn default() -> Self {
        Data { number: 0 }
    }
}

let mut objs: Vec<Handle<Data>> = vec![];
let mut f: Factory<Data> = Factory::new();



for i in 0..5 {
    let handle = f.make_next();

    handle.get_mut().number = i;

    objs.push(handle);
}

for handle in objs {
    println!("{:?}", handle.get());
}
rust borrow-checker borrow

评论

1赞 PitaJ 12/22/2022
你是想做一个字符串interner吗?
0赞 PitaJ 12/22/2022
你可以使用像pinus这样的东西
0赞 Kamil Szot 12/22/2022
@PitaJ我不这么认为。字符串只是一个例子。最终,我希望能够生成和保留任何类型的堆分配数据,并借用那些与“容器”一样长的数据。
1赞 Kamil Szot 12/22/2022
@PitaJ 但是,如果我不能做我要求的事情,这是一个有趣的想法。只需制作东西,将其放入 Vec 中,然后将索引传递到 vec 中,而不是借用。
1赞 BallpointBen 12/22/2022
使用整数(或某种键类型,取决于后备存储是什么)而不是引用听起来绝对是正确的方法。

答:

3赞 Chayim Friedman 12/22/2022 #1

首先,如果您可以访问 interner,则不需要它。但是您可能希望通过共享引用来访问它,因此您确实需要。&mutRefCell

另一种方法是将新类型索引返回到 而不是引用中。这节省了间接内容,但需要访问中间人才能访问被拘禁的字符串,因此它可能不满足要求。这也不允许你在保留对旧字符串的引用时分配新字符串(使用无济于事,它只会恐慌):VecRefCell

use std::ops::Index;

struct StringHandle(usize);

struct Factory {
    pub store: Vec<String>,
}
impl Factory {
    pub fn make_next_string(&mut self) -> StringHandle {
        let len = self.store.len();
        self.store.push(format!("string no. {}", len));
        StringHandle(len)
    }
    pub fn new() -> Factory {
        Factory { store: vec![] }
    }
}

impl Index<StringHandle> for Factory {
    type Output = str;
    fn index(&self, index: StringHandle) -> &Self::Output {
        &self.store[index.0]
    }
}

fn main() {
    let mut f = Factory::new();
    let mut strs: Vec<StringHandle> = vec![];

    for _ in 0..5 {
        let handle = f.make_next_string();
        strs.push(handle);
    }

    for handle in strs {
        println!("{}", &f[handle]);
    }
}

最好的方法是使用竞技场。它允许您生成引用(因此不需要访问内部器即可访问被拘禁的字符串),并在创建新字符串时保留旧的。缺点是它需要使用 crate,因为您可能不想自己实现 arena(这也需要不安全的代码),并且您不能将该 interner 存储在被拘禁的字符串旁边(这是一个自引用结构)。您可以使用typed-arena crate:

use std::cell::Cell;

use typed_arena::Arena;

struct Factory {
    store: Arena<String>,
    len: Cell<u32>,
}

impl Factory {
    pub fn make_next_string(&self) -> &str {
        let len = self.len.get();
        self.len.set(len + 1);
        self.store.alloc(format!("string no. {}", len))
    }
    pub fn new() -> Factory {
        Factory { store: Arena::new(), len: Cell::new(0) }
    }
}

fn main() {
    let f = Factory::new();
    let mut strs: Vec<&str> = vec![];

    for _ in 0..5 {
        let interned = f.make_next_string();
        strs.push(interned);
    }

    for interned in strs {
        println!("{}", interned);
    }
}

您还可以将 s 存储在 arean 中(而不是 s) 优点是更好的缓存访问,因为结构更扁平,并且由于不需要循环和丢弃存储的字符串,interner 本身的删除速度要快得多;缺点是您需要在存储字符串之前复制它们。我推荐 bumpalostrString

use std::cell::Cell;

use bumpalo::Bump;

struct Factory {
    store: Bump,
    len: Cell<u32>,
}

impl Factory {
    pub fn make_next_string(&self) -> &str {
        let len = self.len.get();
        self.len.set(len + 1);
        self.store.alloc_str(&format!("string no. {}", len))
    }
    pub fn new() -> Factory {
        Factory { store: Bump::new(), len: Cell::new(0) }
    }
}

fn main() {
    let f = Factory::new();
    let mut strs: Vec<&str> = vec![];

    for _ in 0..5 {
        let interned = f.make_next_string();
        strs.push(interned);
    }

    for interned in strs {
        println!("{}", interned);
    }
}