0

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?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Kos
  • 1,547
  • 14
  • 23
  • 3
    Looks like you are re-implementing the `IntoIterator` trait from the standard library. But what exactly is your question? What's wrong with the code you have now? – Colonel Thirty Two Jun 03 '22 at 23:55
  • Ignoring the fact that the code does not compile, note that `trait Enumerable<'it, 'it2, It>` in the second code snippet has a very different meaning from `trait Enumerable`, since the former allows multiple `Enumerable` implementations for a single type. Is this actually what you want? – kotatsuyaki Jun 04 '22 at 01:49
  • You probably want to do something like `-> impl Iterator` – PitaJ Jun 04 '22 at 03:23
  • 2
    `impl Trait` is not allowed as the return type of a trait method. One possible solution is to use an associated type, but that may require GAT which is not stabilized yet. – kotatsuyaki Jun 04 '22 at 03:31
  • Point is not reimplementing traits, but avoiding using Boxed structures and OOP. Example is oversimplified version of original problem, please don't focus on that. I've got solution. I will post it. – Kos Jun 04 '22 at 13:10
  • Does this answer your question? [Implementing a trait function that returns generic](https://stackoverflow.com/questions/72271000/implementing-a-trait-function-that-returns-generic) – Chayim Friedman Jun 05 '22 at 23:03
  • Not directly, but that is something related. – Kos Jun 06 '22 at 15:32

2 Answers2

3

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.

Finomnis
  • 18,094
  • 1
  • 20
  • 27
2

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.

Kos
  • 1,547
  • 14
  • 23