如何在Rust中通过嵌套的map调用实现IntoIterator?

pcrecxhr  于 2023-06-23  发布在  其他
关注(0)|答案(2)|浏览(92)

在为自定义结构实现IntoIterator时,我在指定IntoIterator的IntoIter关联类型的类型时遇到了麻烦。因为into_iter函数调用了一个Map,而它本身返回了一个Map,而且如果我没记错的话,闭包默认会推断出一个新的生命周期(或者类似的东西),我陷入了一个我似乎无法理解的通用汤中。这听起来已经很矛盾了,所以我打算发布代码片段,并尝试解释预期的行为。
请注意,我对正在发生的事情感到非常困惑,所以我问的问题可能不合适,或者可以更好地制定,请随时提出一个更好的建议,以便我可以更新它。
在这段代码中,我们有一个结构(SubstitutionBlock),它以简洁的方式表示许多替换(许多SubstitutionEntry)。这里需要将简洁的表示(SubstitutionBlock)“拆分”为单个条目的迭代(多个SubstitutionEntry)。我试图为SubstitutionBlock实现IntoIterator,以产生SubstitutionEntry对象,但就像我说的,我就是不知道如何实现。
在花了整整一个下午的时间之后,我对代码的解决方案比变通/替代逻辑更感兴趣,但无论如何,如果你确实认为有更好的方法来实现这一点,请随时发布它。
为了完整起见,我发布了一个片段,但我相信Playground链接更有用:
Playground Link

use std::{collections::HashMap, iter::Map};

#[derive(Debug)]
pub struct SubstitutionBlock {
    pub id: String,
    pub aliases: HashMap<String, Vec<String>>,
    pub format: Option<String>,
    pub parents: Option<Vec<String>>,
}

#[derive(Debug)]
struct SubstitutionEntry {
    id: String,
    alias: String,
    value: String,
    format: Option<String>,
}

impl IntoIterator for SubstitutionBlock {
    type Item = SubstitutionEntry;
    
    /// HERE IS WHERE I STRUGGLE
    // type IntoIter<'a> = Map<HashMap<String, Vec<String>>, fn((&'a String, &'a Vec<String>)) -> Map<&'a Vec<String>, fn(&String) -> SubstitutionEntry>>;
    type IntoIter = Map<HashMap<String, Vec<String>>, fn((&String, &Vec<String>)) -> Map<&Vec<String>, fn(&String) -> SubstitutionEntry>>;

    fn into_iter(self) -> Self::IntoIter {

        self.aliases
        .into_iter()
        .map(
            |(value, aliases)| aliases.into_iter().map(
                |alias| SubstitutionEntry{
                    id: self.id.to_owned(),
                    alias: alias.to_owned(),
                    value: value.to_owned(),
                    format: self.format.to_owned(),
                }
            )
        )

    }

}

fn main() {
    
    let sb = SubstitutionBlock{
        id: String::from("id0"),
        aliases: HashMap::from([
            ("value0", vec!["alias0, alias1, alias2"]),
        ]),
        format: None,
        parents: None,
    };
    
    for entry in sb.into_iter() {
        println!("{:?}", entry);
    }
    
}
pb3skfrl

pb3skfrl1#

你想做的事有点不可能。
首先,这里是固定的函数体,以便它作为独立函数编译。你的闭包都不需要生命周期,因为它们都采用自己的值。

fn into_iter(block: SubstitutionBlock) -> impl Iterator<Item = SubstitutionEntry> {
    block.aliases.into_iter().flat_map(move |(value, aliases)| {
        let id = block.id.clone();
        let format = block.format.clone();

        aliases.into_iter().map(move |alias| SubstitutionEntry {
            id: id.clone(),
            alias,
            value: value.clone(),
            format: format.clone(),
        })
    })
}

不可能的部分是你不能从trait方法返回不透明类型。你可以用几乎完整的类型来替换impl Iterator<Item = SubstitutionEntry>,但是当你在Map上使用闭包类型时,你会被困在使用impl FnMut,一种不透明类型。现在最好的解决办法就是把它们装箱。你可以装箱封闭,但我已经装箱整个东西,因为它更简单。

type IntoIter = Box<dyn Iterator<Item = SubstitutionEntry>>;

fn into_iter(self) -> Self::IntoIter {
    let iter = into_iter(self);
    Box::new(iter)
}

这就是async-trait所做的。
另一种解决这个问题的方法是创建一个实现Iterator的结构体,并将您的逻辑置于其next方法中,而不是使用map。重新创建flat_map的逻辑有点乏味,但是用定制结构替换一系列迭代器适配器的方法总是可行的。

use std::collections::hash_map::IntoIter as HashIter;
use std::vec::IntoIter as VecIter;

pub struct SubstitutionIter {
    id: String,
    aliases: HashIter<String, Vec<String>>,
    format: Option<String>,
    current: Option<(String, VecIter<String>)>,
}

impl SubstitutionIter {
    fn new(sub: SubstitutionBlock) -> Self {
        let mut aliases = sub.aliases.into_iter();
        let current = aliases.next().map(|(k, v)| (k, v.into_iter()));

        Self {
            id: sub.id,
            aliases,
            format: sub.format,
            current,
        }
    }
}

impl Iterator for SubstitutionIter {
    type Item = SubstitutionEntry;

    fn next(&mut self) -> Option<Self::Item> {
        let Some((alias, values)) = &mut self.current else {
            return None;
        };

        let next = Some(SubstitutionEntry {
            id: self.id.clone(),
            alias: alias.clone(),
            value: values.next().unwrap(),
            format: self.format.clone(),
        });

        if values.len() == 0 {
            self.current = self.aliases.next().map(|(k, v)| (k, v.into_iter()));
        }

        next
    }
}

impl IntoIterator for SubstitutionBlock {
    type Item = SubstitutionEntry;
    type IntoIter = SubstitutionIter;

    fn into_iter(self) -> Self::IntoIter {
        SubstitutionIter::new(self)
    }
}

All of this in playground
相关内容:

3pmvbmvn

3pmvbmvn2#

这里是最惯用的解决方案,这是每晚唯一的。

use mini_alloc::hashbrown::HashMap;

    #[derive(Debug)]
    pub struct SubstitutionBlock {
        pub id: String,
        pub aliases: HashMap<String, Vec<String>>,
        pub format: Option<String>,
        pub parents: Option<Vec<String>>,
    }

    #[derive(Debug)]
    pub struct SubstitutionEntry {
        pub id: String,
        pub alias: String,
        pub value: String,
        pub format: Option<String>,
    }

    impl IntoIterator for SubstitutionBlock {
        type Item = SubstitutionEntry;

        // this requires nightly feature `impl_trait_in_assoc_type`
        type IntoIter = impl Iterator<Item = Self::Item>;

        fn into_iter(self) -> Self::IntoIter {
            let Self {
                id,
                aliases,
                format,
                ..
            } = self;
            aliases.into_iter().flat_map(move |(value, aliases)| {
                let (id, format) = (id.to_owned(), format.to_owned());
                aliases.into_iter().map(move |alias| SubstitutionEntry {
                    id: id.to_owned(),
                    alias: alias.to_owned(),
                    value: value.to_owned(),
                    format: format.to_owned(),
                })
            })
        }
    }

相关问题