Я хотел бы иметь 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());
}
Вы можете использовать что-то вроде pinus
@PitaJ Я так не думаю. Строка - это просто пример. В конечном счете, я хотел бы иметь возможность создавать и хранить любые данные, выделенные кучей, и получать заимствования тех, которые живут до тех пор, пока «контейнер».
@PitaJ Но это интересная идея, если я не могу сделать то, о чем прошу. Просто создайте материал, поместите его в вексель и передайте индекс в вексель вместо заимствования.
Использование целых чисел (или какого-либо типа ключа, в зависимости от того, что является резервным хранилищем) вместо ссылок определенно звучит как правильный путь.
@BallpointBen Хорошо. Я думаю, это хорошая идея. И я могу обернуть фабрику в Rc<RefCell<_>>
, чтобы я мог называть ее методом make_stuff(&mut self)
через .borrow_mut()
без проблем с тем, чтобы мне не разрешалось иметь несколько изменяемых ссылок на нее одновременно.
Во-первых, если у вас есть &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]);
}
}
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);
}
}
Вы также можете хранить str
s внутри области (вместо String
s). Преимуществами являются лучший доступ к кешу, поскольку структура более плоская и гораздо более быстрое удаление самого интернера из-за отсутствия необходимости зацикливаться и удалять сохраненные строки; недостатком является то, что вам нужно скопировать строки перед их сохранением. Рекомендую бампало:
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);
}
}
Вы пытаетесь сделать строковый интерн?