0

I am facing a weird lifetime compilation error while trying to compile following simple XOR encryption Rust code.

pub struct Encrypt<'a> {
    key:&'a[u8],
    current_index: usize,
}

impl<'a> Encrypt<'a> {
    pub fn new<T: ?Sized + AsRef<[u8]>>(key: &'a T) -> Encrypt<'a> {
        Encrypt { key: key.as_ref(), current_index: 0 }
    }

    pub fn encrypt<T, U>(&'a mut self, data: T) -> impl Iterator<Item = u8>
                where T: IntoIterator<Item = U>, U: std::borrow::Borrow<u8> {
        let iter = data.into_iter().map(|b| {
            let val = b.borrow() ^ self.key[self.current_index];
            self.current_index = (self.current_index + 1) % self.key.len();
            val
        });
        iter
    }
}

I am getting following bompilation error:

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
8  | impl<'a> Encrypt<'a> {
   |      -- hidden type `Map<<T as IntoIterator>::IntoIter, [closure@src/lib.rs:36:41: 40:10]>` captures the lifetime `'a` as defined here
...
41 |         iter

This is really strange because the method encrypt() does not return any reference, still the compiler appears to be associating the lifetime of self with it.

1
  • .map() makes a lazy iterator, thus the closure that is used in it has to capture all the objects used inside. In particular you use &'a self in it to access self.key and self.current_index, so the returning iterator is actually tied to this reference and its lifetime Commented Aug 10, 2022 at 19:55

1 Answer 1

2

This is a common problem that can arise from incorrect lifetime annotations. The issue is that you're binding the returned iterator's lifetime to the lifetime of self which is 'a. But you don't care about the lifetime of self, you only care about the lifetime of the reference you're borrowing to it, &mut self, let's call that lifetime 's.

Now we just need to tell the compiler:

impl<'a> Encrypt<'a> {
    pub fn encrypt<'s, T, U>(&'s mut self, data: T) -> impl Iterator<Item = u8> + 's
    //             ^^         ^^                                                ^^^^
    // tie the return value's lifetime to the lifetime for which we burrow `self` 
    // instead of the whole lifetime of `self`.
    where
        T: IntoIterator<Item = U> + 's,
        //                        ^^^^
        // That'll transitively require this bound
        U: std::borrow::Borrow<u8>,
    {
        let iter = data.into_iter().map(|b| {
            let val = b.borrow() ^ self.key[self.current_index];
            self.current_index = (self.current_index + 1) % self.key.len();
            val
        });
        iter
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.