#rust
#Ржавчина
Вопрос:
У меня есть struct Folder
. У меня есть вызванный метод contents
. Я хочу, чтобы этот метод возвращал объект, который поддерживает IntoIterator
, чтобы вызывающий мог просто перейти
for x in folder.contents(){
...
}
Item
Тип (поскольку это то, что возвращает итератор hashmap — см. Чуть ниже)
(amp;OsString, amp;FileOrFolder)
где FileOrFolder
перечисление
enum FileOrFolder{
File(File),
Folder(Folder)
}
Сам итератор должен сначала перечислить a HashMap<OSString, FileOrFolder>
, принадлежащий папке, а затем, во-вторых, перечислить a Vec<File>
. Файлы Vec
создаются «на лету» с помощью содержимого fn
или IntoIterator
вызова, независимо от того, что работает. Я попытался просто использовать chain
, но быстро понял, что это не сработает. Итак, мой грубый набросок того, что я пытаюсь сделать, заключается в следующем:
// the iterator
pub struct FFIter {
files: Vec<FileOrFolder>,
files_iter:Box<dyn Iterator<Item=FileOrFolder>>,
dirs: Box<dyn Iterator<Item = (amp;OsString, amp;FileOrFolder)>>,
dirs_done:bool
}
// the thing returned by the contents fn
struct FolderContents{
folder:amp;Folder
}
// make it iterable
impl IntoIterator for FolderContents {
type Item =(amp;OsString, amp;FileOrFolder);
type IntoIter = FFIter;
fn into_iter(self) -> Self::IntoIter {
let files = self.folder.make_the_files()
FFIter {
files: files, // to keep files 'alive'
files_iter: files.iter(),
dirs: Box::new(self.hashmap.iter()),
dirs_done:false
}
}
}
impl Iterator for FFIter {
type Item = (amp;OsString, amp;FileOrFolder);
fn next(amp;mut self) -> Option<(amp;OsString, amp;FileOrFolder)> {
None // return empty, lets just get the skeleton built
}
}
impl Folder{
pub fn contents(amp;self) -> FolderContents{
FolderContents{folder:amp;self}
}
}
Я знаю, что это полно ошибок, но мне нужно знать, выполнимо ли это вообще. Как вы можете видеть, я даже не пытаюсь написать код, который что-либо возвращает. Я просто пытаюсь получить базовую схему для компиляции.
Я начал заниматься армрестлингом с системой lifetime и дошел до того, что у меня было это
error[E0658]: generic associated types are unstable
--> srcstatefilesfile_or_folder.rs:46:5
|
46 | type Item<'a> =(amp;'a OsString, amp;'a FileOrFolder);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information
Что довольно отстойно, поскольку компилятор сказал, что я должен это сделать.
Я рад продолжать работать над этим, следуя предложениям компилятора / чтение / … Но в прошлом я задавал вопрос в этом направлении, и мне сказали: «конечно, это невозможно сделать». Так должен ли я быть в состоянии заставить это работать?
Тип папки не Copy
является дорогостоящим для клонирования. Тип файла простой (string and i64)
, Copy
и Clone
Я знаю, что мог бы просто заставить вызывающего абонента вызывать две разные итерации и объединять их, но я пытаюсь написать прозрачный модуль замены для вставки в большую существующую кодовую базу.
Если кто-то скажет, что chain()
это должно сработать, это здорово, я сделаю это еще раз.
РЕДАКТИРОВАТЬ Jmp сказал, что цепочка должна работать,
вот что я пробовал
pub fn contents(amp;self) -> Box<dyn Iterator<Item = (amp;OsString, amp;FileOrFolder)> '_> {
let mut files = vec![];
if self.load_done {
for entry in WalkDir::new(amp;self.full_path)
.max_depth(1)
.skip_hidden(false)
.follow_links(false)
.into_iter()
{
let ent = entry.unwrap();
if ent.file_type().is_file() {
if let Some(name) = ent.path().file_name() {
files.push((
name.to_os_string(),
FileOrFolder::File(File {
name: name.to_os_string(),
size: ent.metadata().unwrap().len() as u128,
}),
));
}
}
}
};
Box::new(
self.contents
.iter()
.map(|(k, v)| (k, v))
.chain(files.iter().map(|x| (amp;x.0, amp;x.1))),
)
}
но компилятор правильно жалуется, что «файлы» уничтожаются в конце вызова. Мне нужно, чтобы vec удерживался итератором, а затем удалялся в конце итерации. Сама папка не может содержать файлы — весь смысл здесь в том, чтобы заполнять файлы на лету, это слишком дорого, с точки зрения памяти, для их хранения.
Комментарии:
1.
chain
должно сработать, но вам нужно будет убедиться, что оба итератора имеют одинаковыйItem
тип или используютсяmap
для преобразования одного (или обоих) в соответствующий тип.2. @Jmb, ну, это именно то, что я пытался сделать (map amp; chain) и не смог заставить его работать. Я попробую еще раз
3. @Jmb нет, я не вижу, как это сделать, vec файлов создается на лету. Я обновлю Q, чтобы показать, что я хотел
4. «созданные на лету» и «итератор со ссылками в элементе» обычно вообще не сочетаются.
5. @SebastianRedl — Я на 100% это знаю. Я прошу совета, что делать, я уверен, что смогу сохранить vec «на лету» в самом объекте итератора. Изначально я просил о помощи, но Jmp сказал, что я должен просто использовать цепочку
Ответ №1:
Вы утверждаете, что files
он заполняется на лету, но это именно то, чего не делает ваш код: ваш код предварительно вычисляет files
, прежде чем пытаться его вернуть. Решение состоит в том, чтобы действительно вычислять files
на лету, что-то вроде этого:
pub fn contents(amp;self) -> Box<dyn Iterator<Item = (amp;OsString, amp;FileOrFolder)> '_> {
let files = WalkDir::new(amp;self.full_path)
.max_depth(1)
.skip_hidden(false)
.follow_links(false)
.into_iter()
.filter_map (|entry| {
let ent = entry.unwrap;
if ent.file_type().is_file() {
if let Some(name) = ent.path().file_name() {
Some((
name.to_os_string(),
FileOrFolder::File(File {
name: name.to_os_string(),
size: ent.metadata().unwrap().len() as u128,
}),
))
} else None
} else None
});
self.contents
.iter()
.chain (files)
}
Поскольку вы не дали нам MRE, я не тестировал выше, но я думаю, что он завершится неудачей, потому self.contents.iter()
что возвращает ссылки, тогда files
как возвращает собственные значения. Для исправления этого требуется изменить прототип функции, чтобы возвращать некоторую форму принадлежащих значений, поскольку files
невозможно заставить возвращать ссылки. Я вижу два способа сделать это:
- Проще всего
FileOrFolder
сделать клонируемым и избавиться от ссылок в прототипе:
pub fn contents(amp;self) -> Box<dyn Iterator<Item = (OsString, FileOrFolder)> '_> {
let files = ...;
self.contents
.iter()
.cloned()
.chain (files)
- Или вы можете создать тип оболочки, аналогичный
Cow
than, который может содержать либо ссылку, либо принадлежащее значение:
enum OwnedOrRef<'a, T> {
Owned (T),
Ref (amp;'a T),
}
pub fn contents(amp;self) -> Box<dyn Iterator<Item = (OwnedOrRef::<OsString>, OwnedOrRef::<FileOrFolder>)> '_> {
let files = ...;
self.contents
.iter()
.map (|(k, v)| (OwnedOrRef::Ref (k), OwnedOrRef::Ref (v))
.chain (files
.map (|(k, v)| (OwnedOrRef::Owned (k),
OwnedOrRef::Owned (v)))
}
Вы даже можете использовать Cow
, если FileOrFolder
можете реализовать ToOwned
.
Комментарии:
1. ty — когда я сказал «на лету», я имел в виду, что данные не являются полем в структуре. Он генерируется по требованию. Было бы тривиально, если бы files был членом папки
2. Я вижу, что ты делаешь, тай.