Kos
Kos

Reputation: 1778

Non-dyn iterable?

I am trying to avoid lifetimes because I still don't have a good understanding of the concept. I am reading this wonderful article and it clarifies many misunderstandings. Although I am not sure I can solve the problem.

I know how to implement iterable collection on dyn. Playground:

use std::collections::HashMap;

pub trait Enumerable {
    fn elements<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = (i32, &String)> + 'b>
    where
        'a: 'b;
}

#[derive(Debug)]
struct Container {
    pub map: HashMap<i32, String>,
}

impl Enumerable for Container {
    fn elements<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = (i32, &String)> + 'b>
    where
        'a: 'b,
    {
        Box::new(self.map.iter().map(|el| (*el.0, el.1)))
    }
}

My attempt to implement the same code without dyn. Playground:

use std::collections::HashMap;

pub trait Enumerable<'it, 'it2, It>
where
    It: Iterator<Item = &'it2 (i32, &'it String)>,
    'it: 'it2,
{
    fn elements<'a, 'b>(&'a self) -> It
    where
        'a: 'b;
}

#[derive(Debug)]
struct Container {
    pub map: HashMap<i32, String>,
}

impl<'it, 'it2> Enumerable<'it, 'it2, core::slice::Iter<'it, (i32, &String)>> for Container {
    fn elements<'a, 'b>(&'a self) -> core::slice::Iter<'a, (i32, &'b String)>
    where
        'a: 'b,
    {
        self.map.iter().map(|el| (*el.0, el.1))
    }
}

I was thinking about using impls but there is a restriction on using it in a trait. What is wrong with the code? What other useful articles can you recommend?

Upvotes: 0

Views: 113

Answers (2)

Kos
Kos

Reputation: 1778

There is a solution to the original problem with GAT and TAIT which are not part of stable channgel for today.

Solution is

mod mod1 {

    pub trait Enumerable {
        type It<'it>: Iterator<Item = (i32, &'it String)>
        where
            Self: 'it;

        fn elements(&self) -> Self::It<'_>;
    }
}

//

impl mod1::Enumerable for Container {
    type It<'it> = impl Iterator<Item = (i32, &'it String)>;
    fn elements(&self) -> Self::It<'_> {
        self.map.iter().map(|el| (*el.0, el.1))
    }
}

Full solution

There are alternative solutions, but this one works even if the trait is not part of your crate.

Also, I should note, that if possible to avoid using lifetimes you can implement IntoIterator for your &Container:

impl< 'it > IntoIterator for &'it Container
{
  type Item = ( &'it i32, &'it String );
  type IntoIter = std::collections::hash_map::Iter< 'it, i32, String >;
  fn into_iter( self ) -> Self::IntoIter
  {
    self.map.iter()
  }
}

Full solution of a tweaked problem

Because the lifetime is dropped that works even on stable Rust. Most probably you want to have your own InotIterator-like trait, especially if there is more than a single way how can you iterate your container, but if not you can simply implement standard IntoIterator for reference.

Upvotes: 2

Finomnis
Finomnis

Reputation: 22738

Apart of the fact that I don't think you need two separate lifetimes 'a and 'b, your first code example already looks quite promising.

Then, once you have only one lifetime, Rust can figure out lifetimes without any annotations:

use std::collections::HashMap;

pub trait Enumerable {
    fn elements(&self) -> Box<dyn Iterator<Item = (i32, &String)> + '_>;
}

#[derive(Debug)]
struct Container {
    pub map: HashMap<i32, String>,
}

impl Enumerable for Container {
    fn elements(&self) -> Box<dyn Iterator<Item = (i32, &String)> + '_> {
        Box::new(self.map.iter().map(|el| (*el.0, el.1)))
    }
}

I know this is an XY-problem answer, but maybe it helps anyway. Your main reasoning behind not using dyn was to not deal with lifetimes, so I thought this might be relevant.

Upvotes: 3

Related Questions