Question

I'm learning about named lifetimes in Rust, and I'm having trouble understanding what they represent when they are used in the implementation of a trait. Specifically, I'm having trouble understanding this piece of code from libserialize/hex.rs. I've removed some comments for brevity's sake.

pub trait ToHex {
    fn to_hex(&self) -> ~str;
}

static CHARS: &'static[u8] = bytes!("0123456789abcdef");

impl<'a> ToHex for &'a [u8] {
    fn to_hex(&self) -> ~str {
        let mut v = slice::with_capacity(self.len() * 2);
        for &byte in self.iter() {
            v.push(CHARS[(byte >> 4) as uint]);
            v.push(CHARS[(byte & 0xf) as uint]);
        }

        unsafe {
             str::raw::from_utf8_owned(v)
        }
    }
}

I understand the 'static lifetime in the CHARS definition, but I'm stumped on the lifetime defined in the ToHex implementation. What do named lifetimes represent in the implementation of a trait?

Was it helpful?

Solution

In that particular case—not much. &[u8] is not a completely specified type because the lifetime is missing, and implementations must be for fully specified types. Thus, the implementation is parameterised over the arbitrary (for the generic parameter is unconstrained) lifetime 'a.

In this case, you don't use it again. There are cases where you will, however—when you wish to constrain a function argument or return value to the same lifetime.

You can then write things like this:

impl<'a, T> ImmutableVector<'a, T> for &'a [T] {
    fn head(&self) -> Option<&'a T> {
        if self.len() == 0 { None } else { Some(&self[0]) }
    }
    …
}

That means that the return value will have the same lifetime as self, 'a.

Incidentally, just to mess things up, the lifetime could be written manually on each function:

impl<'a, T> ImmutableVector<'a, T> for &'a [T] {
    fn head<'a>(&'a self) -> Option<&'a T> {
        if self.len() == 0 { None } else { Some(&self[0]) }
    }
    …
}

… and that demonstrates that having to specify the lifetime of the type that you are implementing for is just so that the type is indeed fully specified. And it allows you to write a little bit less for all the functions inside that use that lifetime.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top