Christian Svensson
Christian Svensson

Reputation: 133

Specifying lifetime of the associated type in the Iterator trait

I'm a Rust newbie and I'm trying to figure out what the best way to do the following in Rust is:

struct ThingIterator {
    current: String,
    stop: String,
}

impl Iterator for ThingIterator {
    type Item = &str;
    fn next(&mut self) -> Option<&str> {
        if self.current == self.stop {
            return None;
        }
        // For testing
        self.current = self.stop;
        Some(&self.current)
    }
}

fn main() {
    let pi = ThingIterator {
        current: String::from("Ask"),
        stop: String::from("Zoo"),
    };
    println!("Number of things={}", pi.count());
}

My error is:

error[E0106]: missing lifetime specifier
 --> src/main.rs:7:17
  |
7 |     type Item = &str;
  |                 ^ expected lifetime parameter

error: aborting due to previous error

This makes sense, I need to specify how long the reference returned from next() is going to be valid. I'm guessing for the function itself it's fine as the lifetime is elissed (not sure about the conjugation of elission) - but I somehow need to define the lifetime of the "type Item = &str" row.

In my case it will be valid as long as "current" is valid, i.e. the same lifetime as "self".

I haven't seen anything in the Rust book or other documentation that helps me figure out this case.

P.S. Sorry if I'm butchering the nomenclature, I'm very much new to Rust. Thanks

Upvotes: 4

Views: 779

Answers (2)

SCappella
SCappella

Reputation: 10424

The lifetime on &mut self in next isn't in scope when defining the type Item, so Item can't depend on that lifetime. Instead, it's typical to have ThingIterator hold references instead of owned data. If there's still a structure that has owned data, you'll probably implement IntoIterator for &OwnsData to convert into the type that uses references.

// ThingIterator is now generic in the lifetime 'a
// and it holds references rather than owned Strings.
struct ThingIterator<'a> {
    current: &'a str,
    stop: &'a str,
}

impl<'a> Iterator for ThingIterator<'a> {
    // Now we can use the lifetime from ThingIterator here.
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        if self.current == self.stop {
            return None;
        }
        // For testing
        self.current = self.stop;
        Some(self.current)
    }
}

// Typically, you'll have a type that owns its data
// Like Vec<T>, HashSet<T>, etc.
struct OwnsData {
    current: String,
    stop: String,
}

impl OwnsData {
    // We'll have the traditional method that takes a reference
    // to self and returns an iterator over references into self.

    // Explicit lifetimes aren't needed, but it might help with understanding.
    // fn iter<'a>(&'a self) -> ThingIterator<'a> {

    fn iter(&self) -> ThingIterator {
        ThingIterator {
            current: &self.current,
            stop: &self.stop,
        }
    }
}

// Then, we'll implement IntoIterator for references to OwnsData
// using the OwnsData::iter method defined above.
// This is helpful because for loops and many iterator methods
// use IntoIterator to work.
impl<'a> IntoIterator for &'a OwnsData {
    // We'll be converting into ThingIterator
    type IntoIter = ThingIterator<'a>;
    type Item = &'a str;

    fn into_iter(self) -> ThingIterator<'a> {
        self.iter()
    }
}

fn main() {
    let pi = ThingIterator {
        current: "Ask",
        stop: "Zoo",
    };
    println!("Number of things={}", pi.count());

    // Alternatively, we could start with Strings
    // and use OwnsData
    let tau = OwnsData {
        current: "Ask".to_string(),
        stop: "Zoo".to_string(),
    };
    println!("Number of things={}", tau.iter().count());
}

(playground)

See also

P.S. the word you're looking for is "elided".

Upvotes: 6

edwardw
edwardw

Reputation: 13942

You can't, yet. This would require generic associated types (GATs). As of today, it is still just a RFC.

The current Iterator / Stream API is sometimes called "detached":

The idea is that Item that gets returned by Stream is “detached” from self, which means that it can be stored and moved about independently from self.

Hypothetically, after GAT is landed, one would be able to write something like:

trait AttachedStream {
    type Item<'s> where Self: 's;
    //       note the `'s` here!

    fn poll_next<'s>(
        self: Pin<&'s mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Self::Item<'s>>>;
    //                         ^^^^
    // `'s` is the lifetime of the `self` reference.
    // Thus, the `Item` that gets returned may
    // borrow from `self`.
}

That's exactly what you want. Check out Niko's async interview #2 for more interesting details.

Upvotes: 3

Related Questions