Skip to content

Lifetime parameters on self + impl Traits requires unexpected workaround #80518

@snoyberg

Description

@snoyberg

This may simply be that I'm misunderstanding something. But I was surprised by the workaround necessary to make the following code work. When working on the Xorcism exercise in Exercism, I needed to return an impl Iterator<Item=u8>. This was complicated by the presence of two different lifetime parameters (the 'a parameter from the trait for the key field, which is a &'a [u8], and the 'b parameter for the &'b mut self). With the following initial code:

use std::borrow::Borrow;

#[derive(Clone)]
pub struct Xorcism<'a> {
    key: &'a [u8],
    index: usize,
}

pub trait MungeOutput: Iterator<Item = u8> + ExactSizeIterator {}
impl<'a, T> MungeOutput for T where T: Iterator<Item = u8> + ExactSizeIterator {}

fn next_key(key: &[u8], index: &mut usize) -> u8 {
    let b = key[*index];
    *index += 1;
    if *index >= key.len() {
        *index = 0;
    }
    b
}

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

    pub fn munge_in_place(&mut self, data: &mut [u8]) {
        for b in data {
            *b ^= next_key(self.key, &mut self.index);
        }
    }

    pub fn munge<'b, Data>(&'b mut self, data: Data) -> impl Iterator<Item=u8> + 'b
    where
        Data: IntoIterator,
        Data::Item: Borrow<u8>,
        Data::IntoIter: 'b,
    {
        data.into_iter().map(move |b| *b.borrow() ^ next_key(&self.key, &mut self.index))
    }
}

I got the error message:

error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
  --> src\lib.rs:35:57
   |
35 |     pub fn munge<'b, Data>(&'b mut self, data: Data) -> impl Iterator<Item=u8> + 'b
   |                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: hidden type `Map<<Data as IntoIterator>::IntoIter, [closure@src\lib.rs:41:30: 41:89]>` captures the lifetime `'a` as defined on the impl at 21:6
  --> src\lib.rs:21:6
   |
21 | impl<'a> Xorcism<'a> {
   |      ^^

However, if I replaced the line:

data.into_iter().map(move |b| *b.borrow() ^ next_key(&self.key, &mut self.index))

With the non-obvious and less intuitive:

let key = &self.key;
let index = &mut self.index;
data.into_iter().map(move |b| *b.borrow() ^ next_key(key, index))

The code compiled and ran as expected. I may be missing something, but I can't see a reason why borrowing the fields from the structure would be safe, but borrowing the structure itself would be dangerous.

Note that the original goal was to make next_key a method of the Xorcism struct, so that the best version of the code would read:

data.into_iter().map(move |b| *b.borrow() ^ self.next_key())

Meta

rustc --version --verbose:

> rustc --version --verbose
rustc 1.48.0 (7eac88abb 2020-11-16)
binary: rustc
commit-hash: 7eac88abb2e57e752f3302f02be5f3ce3d7adfb4
commit-date: 2020-11-16
host: x86_64-pc-windows-msvc
release: 1.48.0
LLVM version: 11.0

Also occurs on nightly:

> rustc +nightly --version --verbose
rustc 1.51.0-nightly (158f8d034 2020-12-29)
binary: rustc
commit-hash: 158f8d034b15e65ba8dc0d066358dd0632bfcd6e
commit-date: 2020-12-29
host: x86_64-pc-windows-msvc
release: 1.51.0-nightly

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.A-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions