我可以创建一个自定义迭代器来对一个序列进行迭代,然后对另一个序列进行迭代链不起作用

问题描述

我有一个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是由内容fnIntoIterator调用动态创建的,无论可行的方法。我尝试仅使用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)copyClone

我知道我可以简单地使调用调用两个不同的迭代并将其合并,但是我正在尝试编写一个透明的替换模块以放入大型现有代码库中。

如果有人说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