问题描述
我有一个struct Folder
。我有一个称为contents
的方法。我希望该方法返回支持IntoIterator
的对象,以便调用者可以直接访问
for x in folder.contents(){
...
}
Item
类型是(因为这是哈希映射迭代器返回的内容-稍低一点)
(&Osstring,&FileOrFolder)
其中FileOrFolder
是一个枚举
enum FileOrFolder{
File(File),Folder(Folder)
}
迭代器本身需要首先枚举文件夹拥有的HashMap<Osstring,FileOrFolder>
,然后第二次枚举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 = (&Osstring,&FileOrFolder)>>,dirs_done:bool
}
// the thing returned by the contents fn
struct FolderContents{
folder:&Folder
}
// make it iterable
impl IntoIterator for FolderContents {
type Item =(&Osstring,&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 = (&Osstring,&FileOrFolder);
fn next(&mut self) -> Option<(&Osstring,&FileOrFolder)> {
None // return empty,lets just get the skeleton built
}
}
impl Folder{
pub fn contents(&self) -> FolderContents{
FolderContents{folder:&self}
}
}
我知道这到处都是错误,但是我需要知道这是否完全可行。如您所见,我什至不尝试编写返回任何内容的代码。我只是想让基本轮廓得以编译。
我开始使用终生系统进行手臂摔跤,直到达到这个目的
error[E0658]: generic associated types are unstable
--> src\state\files\file_or_folder.rs:46:5
|
46 | type Item<'a> =(&'a Osstring,&'a FileOrFolder);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information
有点烂,因为那是编译器所说的。
我很高兴继续遵循编译器的建议/阅读/ ...但是在过去,我按照这些思路发布了一个问题,并被告知-“当然不能完成” 。那我应该能够完成这项工作吗?
文件夹类型不是copy
,而且克隆成本很高。文件类型为简单的(string and i64)
,copy
和Clone
我知道我可以简单地使调用者调用两个不同的迭代并将其合并,但是我正在尝试编写一个透明的替换模块以放入大型现有代码库中。
如果有人说chain()
应该很好,那么我会再谈谈。
EDIT Jmp说链条应该工作,
这是我尝试过的
pub fn contents(&self) -> Box<dyn Iterator<Item = (&Osstring,&FileOrFolder)> + '_> {
let mut files = vec![];
if self.load_done {
for entry in WalkDir::new(&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| (&x.0,&x.1))),)
}
,但是编译器正确地抱怨说,“文件”在调用结束时被破坏了。我需要将vec保留在迭代器中,然后在迭代结束时将其删除。文件夹本身无法保存文件-此处的全部要点是即时填充文件,这太昂贵了,不适合存储它们。
解决方法
您声称files
是即时填充的,但这恰恰是您的代码不所做的事情:您的代码会在尝试返回之前files
进行预先计算。解决方案是即时真正计算files
,如下所示:
pub fn contents(&self) -> Box<dyn Iterator<Item = (&OsString,&FileOrFolder)> + '_> {
let files = WalkDir::new(&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(&self) -> Box<dyn Iterator<Item = (OsString,FileOrFolder)> + '_> {
let files = ...;
self.contents
.iter()
.cloned()
.chain (files)
- 或者您可以使包装类型类似于
Cow
,而不是可以保存引用或拥有的值:
enum OwnedOrRef<'a,T> {
Owned (T),Ref (&'a T),}
pub fn contents(&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
可以实现ToOwned
,则甚至可以使用FileOrFolder
。