Как создать фабрику, которая динамически создает значения и возвращает им займы?

Я хотел бы иметь struct под названием Фабрика, которая динамически производит новые Strings, хранит их внутри себя и возвращает &str заимствования из них, которые живут столько же, сколько и сама Фабрика.

Я пытался сохранить новые значения внутри Vec, но по мере роста Vec заимствования элементов становились недействительными, поэтому они не жили достаточно долго. Я пытался завернуть их в Boxes, RefCells, но столкнулся с теми же проблемами.

Я также хотел бы вызвать этот фабричный метод внутри цикла, чтобы я мог создавать новую строку на каждой итерации и заимствовать ее, чтобы где-то хранить.


Есть крейт 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());
}

Вы пытаетесь сделать строковый интерн?

PitaJ 21.12.2022 21:52

Вы можете использовать что-то вроде pinus

PitaJ 21.12.2022 21:56

@PitaJ Я так не думаю. Строка - это просто пример. В конечном счете, я хотел бы иметь возможность создавать и хранить любые данные, выделенные кучей, и получать заимствования тех, которые живут до тех пор, пока «контейнер».

Kamil Szot 21.12.2022 22:25

@PitaJ Но это интересная идея, если я не могу сделать то, о чем прошу. Просто создайте материал, поместите его в вексель и передайте индекс в вексель вместо заимствования.

Kamil Szot 21.12.2022 22:33

Использование целых чисел (или какого-либо типа ключа, в зависимости от того, что является резервным хранилищем) вместо ссылок определенно звучит как правильный путь.

BallpointBen 21.12.2022 23:46

@BallpointBen Хорошо. Я думаю, это хорошая идея. И я могу обернуть фабрику в Rc<RefCell<_>>, чтобы я мог называть ее методом make_stuff(&mut self) через .borrow_mut() без проблем с тем, чтобы мне не разрешалось иметь несколько изменяемых ссылок на нее одновременно.

Kamil Szot 22.12.2022 00:01
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
6
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, если у вас есть &mut доступ к интерне, вам не нужно RefCell на ней. Но вы, вероятно, захотите получить к нему доступ через общие ссылки, поэтому вам это нужно.

Другой способ — вернуть в Vec новый тип индекса вместо ссылок. Это сохраняет косвенность, но требует доступа к интернированному для доступа к интернированной строке, поэтому он может не соответствовать требованиям. Это также не позволяет вам выделять новые строки, пока вы сохраняете ссылки на старые (использование RefCell не поможет, это просто вызовет панику):

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]);
    }
}

Лучше всего использовать арену . Это позволяет вам получать ссылки (и, следовательно, не требует доступа к внутреннему для доступа к интернированным строкам) и сохранять старые при создании новых. Недостатки заключаются в том, что для этого требуется использование крейта, так как вы, вероятно, не хотите реализовывать арену самостоятельно (это также требует небезопасного кода), и что вы не можете хранить этот интерн вместе с интернированными строками (это самореферентный структура). Для этого вы можете использовать ящик typed-arena:

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);
    }
}

Вы также можете хранить strs внутри области (вместо Strings). Преимуществами являются лучший доступ к кешу, поскольку структура более плоская и гораздо более быстрое удаление самого интернера из-за отсутствия необходимости зацикливаться и удалять сохраненные строки; недостатком является то, что вам нужно скопировать строки перед их сохранением. Рекомендую бампало:

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);
    }
}

Другие вопросы по теме