noisecapella
noisecapella

Reputation: 814

Lifetime for Iterator field

In learning this new fascinating language I wrote this code which outputs 0 through 10 multiplied by 3:

pub struct Multiplier {
    factor : int,
    current : int
}
impl Multiplier {
    pub fn new(factor : int) -> Multiplier {
        Multiplier {
            factor: factor,
            current: 0
        }
    }
}
impl Iterator<int> for Multiplier {
    fn next(&mut self) -> Option<int> {
        if self.current > 10 {
            None
        }
        else {
            let current = self.current;
            self.current += 1;
            Some(current * self.factor)
        }
    }
}

struct Holder {
    x : Multiplier
}
impl Holder {
    pub fn new(factor : int) -> Holder {
        Holder {
            x : Multiplier::new(factor)
        }
    }

    fn get_iterator(&self) -> Multiplier {
        self.x
    }
}

fn main() {
    let mut three_multiplier = Holder::new(3).get_iterator();
    for item in three_multiplier {
        println!("{}", item);
    }
}

If I change Holder from this

struct Holder {
    x : Multiplier
}

to this:

struct Holder {
    x : Iterator<int>
}

I get a compilation warning:

<anon>:27:9: 27:22 error: explicit lifetime bound required
<anon>:27     x : Iterator<int>
                  ^~~~~~~~~~~~~

Can anyone explain why changing the field type requires an explicit lifetime? I know how to mark the lifetimes but I'm not sure why the compiler wants me to do this.

Upvotes: 3

Views: 456

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65832

You can add a lifetime bound this way:

struct Holder<'a> {
    x: Iterator<int>+'a
}

Note however that this way, the Holder struct is unsized, because it contains a trait object, which is unsized. This severely limits what you can do with the type: for example, you can not return a Holder directly.

You can fix this by making Holder accept a type parameter:

struct Holder<T> where T: Iterator<int> {
    x : T
}

Let's change get_iterator to use the type parameter:

impl<T> Holder<T> where T: Iterator<int>+Copy {
    fn get_iterator(&self) -> T {
        self.x
    }
}

Note: I added the Copy bound here because get_iterator currently returns a copy. You could avoid the bound by making get_iterator return a &T instead.

As for new, you'll have to completely redesign it. If we keep it as is, the compiler gives error if we call it as Holder::new because Holder now has a type parameter, and the compiler cannot infer a type because new doesn't use any. To solve this, we can make new generic by using a trait to provide the constructor:

trait FactorCtor {
    fn new(factor: int) -> Self;
}

And then changing new to use that trait:

impl<T> Holder<T> where T: Iterator<int>+FactorCtor {
    pub fn new(factor : int) -> Holder<T> {
        Holder {
            x : FactorCtor::new(factor)
        }
    }
}

Because we have only one implementation of FactorCtor in this program, the compiler manages to infer T when calling Holder::new. If we add another implementation, e.g. Adder:

pub struct Adder {
    factor : int,
    current : int
}
impl FactorCtor for Adder {
    fn new(factor: int) -> Adder {
        Adder {
            factor: factor,
            current: 0
        }
    }
}
impl Iterator<int> for Adder {
    fn next(&mut self) -> Option<int> {
        if self.current > 10 {
            None
        }
        else {
            let current = self.current;
            self.current += 1;
            Some(current + self.factor)
        }
    }
}

Then we get a compiler error:

<anon>:72:9: 72:29 error: unable to infer enough type information about `_`; type annotations required
<anon>:72     let mut three_multiplier = Holder::new(3).get_iterator();
                  ^~~~~~~~~~~~~~~~~~~~

We can fix this by specifying T explicitly:

fn main() {
    let mut three_multiplier = Holder::<Multiplier>::new(3).get_iterator();
    for item in three_multiplier {
        println!("{}", item);
    }
}

Upvotes: 4

Related Questions