Как я могу создать итератор & T из & Vec <T>или Vec <& T>? - PullRequest
8 голосов
/ 22 сентября 2019

У меня есть enum с двумя вариантами.Либо он содержит ссылку на Vec из String с, либо содержит Vec ссылки на String с:

enum Foo<'a> {
    Owned(&'a Vec<String>),
    Refs(Vec<&'a String>),
}

Я хочу перебрать ссылки на Strings в этом перечислении.

Я пытался реализовать метод на Foo, но не знаю, как заставить его вернуть правильный итератор:

impl<'a> Foo<'a> {
    fn get_items(&self) -> Iter<'a, String> {
        match self {
            Foo::Owned(v) => v.into_iter(),
            Foo::Refs(v) => /* what to put here? */,
        }
    }
}

fn main() {
    let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
    let foo = Foo::Owned(&test);

    for item in foo.get_items() {
        // item should be of type &String here
        println!("{:?}", item);
    }
}

детская площадка

Что такое идиоматический метод для достижения этой абстракции над &Vec<T> и Vec<&T>?get_items также может возвращать что-то другое, если оно реализует черту IntoIterator, чтобы я мог использовать ее в цикле for.

Ответы [ 5 ]

6 голосов
/ 22 сентября 2019

Вы не можете просто использовать для этого тип std::slice::Iter.

Если вы не хотите копировать строки или вектор, вам придется реализовать собственный итератор, например:

struct FooIter<'a, 'b> {
    idx: usize,
    foo: &'b Foo<'a>,
}

impl<'a, 'b> Iterator for FooIter<'a, 'b> {
    type Item = &'a String;
    fn next(&mut self) -> Option<Self::Item> {
        self.idx += 1;
        match self.foo {
            Foo::Owned(v) => v.get(self.idx - 1),
            Foo::Refs(v) => v.get(self.idx - 1).map(|s| *s),
        }
    }
}

impl<'a, 'b> Foo<'a> {
    fn get_items(&'b self) -> FooIter<'a, 'b> {
        FooIter { idx: 0, foo: self }
    }
}

fn main() {
    let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()];
    let foo = Foo::Owned(&test);
    for item in foo.get_items() {
        println!("{:?}", item);
    }
    let a = "a".to_string();
    let b = "b".to_string();
    let test: Vec<&String> = vec![&a, &b];
    let foo = Foo::Refs(test);
    for item in foo.get_items() {
        println!("{:?}", item);
    }
}
4 голосов
/ 22 сентября 2019

Существует удобный ящик, auto_enums, который может сгенерировать тип для вас, чтобы у функции могло быть несколько возвращаемых типов, если они реализуют одну и ту же черту.Это похоже на код в ответ Дени Сегюре , за исключением того, что все это сделано для вас макросом auto_enum:

use auto_enums::auto_enum;

impl<'a> Foo<'a> {
    #[auto_enum(Iterator)]
    fn get_items(&self) -> impl Iterator<Item = &String> {
        match self {
            Foo::Owned(v) => v.iter(),
            Foo::Refs(v) => v.iter().copied(),
        }
    }
}

Добавьте зависимость, добавив ее в Cargo.toml:

[dependencies]
auto_enums = "0.6.3"
2 голосов
/ 22 сентября 2019

Если вы не хотите реализовывать свой собственный итератор, для этого вам нужна динамическая диспетчеризация, потому что вы хотите вернуть разные итераторы в зависимости от варианта enum.

Нам нужен объект-признак (&dyn Trait, &mut dyn Trait или Box<dyn Trait>) для использования динамической отправки:

impl<'a> Foo<'a> {
    fn get_items(&'a self) -> Box<dyn Iterator<Item = &String> + 'a> {
        match self {
            Foo::Owned(v) => Box::new(v.into_iter()),
            Foo::Refs(v) => Box::new(v.iter().copied()),
        }
    }
}

.copied() преобразует Iterator<Item = &&String> в Iterator<Item = &String>, поэтомуэто на самом деле ничего не копирует:)

1 голос
/ 23 сентября 2019

В идеале вы хотели бы:

fn get_items(&self) -> impl Iterator<Item = &String> {
    match self {
        Foo::Owned(v) => v.into_iter(),
        Foo::Refs(v)  => v.iter().copied(),
    }
}

Вызов copied здесь для преобразования Iterator<Item = &&String> в Iterator<Item = &String>, который мы хотим.Это не работает, потому что два спичечных плеча имеют разные типы:

error[E0308]: match arms have incompatible types
  --> src/main.rs:12:30
   |
10 | /         match self {
11 | |             Foo::Owned(v) => v.into_iter(),
   | |                              ------------- this is found to be of type `std::slice::Iter<'_, std::string::String>`
12 | |             Foo::Refs(v)  => v.iter().copied(),
   | |                              ^^^^^^^^^^^^^^^^^ expected struct `std::slice::Iter`, found struct `std::iter::Copied`
13 | |         }
   | |_________- `match` arms have incompatible types
   |
   = note: expected type `std::slice::Iter<'_, std::string::String>`
              found type `std::iter::Copied<std::slice::Iter<'_, &std::string::String>>`

Вы можете исправить эту ошибку благодаря itertools или eitherящики, которые содержат удобный адаптер под названием Either (*), который позволяет динамически выбирать между двумя итераторами:

fn get_items(&self) -> impl Iterator<Item = &String> {
    match self {
        Foo::Owned(v) => Either::Left(v.into_iter()),
        Foo::Refs(v)  => Either::Right(v.iter().copied()),
    }
}

детская площадка

1 голос
/ 22 сентября 2019

Несколько вещей, которые вы должны знать в первую очередь:

  • У вас наверняка будут два разных итератора, потому что это разные базовые типы, по которым вы перебираете.Поэтому я собираюсь использовать Box<dyn Iterator<Item = &'a _>>, но не стесняйтесь использовать enum, если это приведет к количественному падению производительности.
  • Вам необходимо ввести здесь время жизни self, потому что, еслимы возвращаем итератор, время жизни которого 'a, но 'a > 'self?Поэтому мы создаем новое время жизни (которое я назову 'b.).
  • Теперь это просто вопрос спорных слоев:

Вот реализация, использующаяоригинальные типы:

enum Foo<'a> {
    Owned(&'a Vec<String>),
    Refs(Vec<&'a String>)
}

impl<'a> Foo<'a> {
    fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a String> + 'b> {
        match self {
            Foo::Owned(v) => //v: &'a Vec<String>
                Box::new(
                    v.iter() //Iterator<Item = &'a String> -- Good!
                ),
            Foo::Refs(v) => //v: Vec<&'a String>
                Box::new(
                    v.iter() //Iterator<Item = &'b &'a String> -- Bad!
                        .map(|x| *x) //Iterator<Item = &'a String> -- Good!
                ),
        }
    }
}

Эти типы на самом деле не похожи на ржавчину (или, более формально, идиоматические ), поэтому вот эта версия с использованием срезов и str s:

enum Foo<'a> {
    Owned(&'a [String]),
    Refs(Vec<&'a str>)
}

impl<'a> Foo<'a> {
    fn get_items<'b>(&'b self) -> Box<dyn Iterator<Item = &'a str> + 'b> {
        match self {
            Foo::Owned(v) => 
                Box::new(
                    v.into_iter()
                        .map(|x| &**x) //&'a String -> &'a str
                ),
            Foo::Refs(v) =>
                Box::new(
                    v.iter()
                        .map(|x| *x) //&'b &'a str -> &'a str
                )/* what to put here? */,
        }
    }
}

Playground

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...