Search code examples
rustiteratorborrow-checker

chunks() still borrowed when returning iterator


Working on base85 decoding using iterators. Preliminary implementation, so the algorithm may not be correct - just trying to get past the borrow checker for now.

pub fn decode(indata: impl IntoIterator<Item=impl Borrow<u8>> + 'static) -> impl Iterator<Item=Result<u8>> {
    #[inline]
    fn char85_to_byte(c: u8) -> Result<u8> {
        match c {
            b'0'..=b'9' => Ok(c - b'0'),
            b'A'..=b'Z' => Ok(c - b'A' + 10),
            b'a'..=b'z' => Ok(c - b'a' + 36),
            b'!' => Ok(62),
            b'#' => Ok(63),
            b'$' => Ok(64),
            b'%' => Ok(65),
            b'&' => Ok(66),
            b'(' => Ok(67),
            b')' => Ok(68),
            b'*' => Ok(69),
            b'+' => Ok(70),
            b'-' => Ok(71),
            b';' => Ok(72),
            b'<' => Ok(73),
            b'=' => Ok(74),
            b'>' => Ok(75),
            b'?' => Ok(76),
            b'@' => Ok(77),
            b'^' => Ok(78),
            b'_' => Ok(79),
            b'`' => Ok(80),
            b'{' => Ok(81),
            b'|' => Ok(82),
            b'}' => Ok(83),
            b'~' => Ok(84),
            v => Err(Error::UnexpectedCharacter(v)),
        }
    }

    indata
        .into_iter()
        .map(|v|*v.borrow())
        .filter(|v| !(*v == 32 || *v == 10 || *v == 11 || *v == 13))
        .chunks(5)
        .into_iter()
        .map(|mut v| {
            let (a,b,c,d,e) = (v.next(), v.next(), v.next(), v.next(), v.next());
            let accumulator = u32::from(char85_to_byte(a.unwrap())?)
                + u32::from(b.map_or(Err(Error::UnexpectedEnd), char85_to_byte)?) * 85u32.pow(1)
                + u32::from(c.map_or(Ok(0), char85_to_byte)?) * 85u32.pow(2)
                + u32::from(d.map_or(Ok(0), char85_to_byte)?) * 85u32.pow(3)
                + u32::from(e.map_or(Ok(0), char85_to_byte)?) * 85u32.pow(4);
            Ok([
                Some((accumulator >> 24) as u8),
                c.map(|_|(accumulator >> 16) as u8),
                d.map(|_|(accumulator >> 8) as u8),
                e.map(|_|accumulator as u8)
            ])
        })
        .flatten_ok()
        .filter_map_ok(|v| v)
}

However this gives the following error:

error[E0716]: temporary value dropped while borrowed
   --> src/lib.rs:96:5
    |
96  |        indata
    |   _____^
    |  |_____|
    | ||
97  | ||         .into_iter()
98  | ||         .map(|v|*v.borrow())
99  | ||         .filter(|v| !(*v == 32 || *v == 10 || *v == 11 || *v == 13))
100 | ||         .chunks(5)
    | ||__________________^ creates a temporary which is freed while still in use
...   |
114 | |              ])
115 | |          })
    | |___________- argument requires that borrow lasts for `'static`
...
118 |    }
    |    - temporary value is freed at the end of this statement

For more information about this error, try `rustc --explain E0716`.

What's still borrowed? Everything I can think of should be getting passed by value.

Much appreciated.


Solution

  • I guess that the missing methods are coming from the de-facto standard iteration library, namely, itertools. Therefore, the minimized example is this:

    use itertools::Itertools;
    
    pub fn decode(indata: Vec<u8>) -> impl Iterator<Item = u8> {
        indata.into_iter().chunks(2).into_iter().flatten()
    }
    

    Playground

    Errors:

    error[E0716]: temporary value dropped while borrowed
     --> src/lib.rs:4:5
      |
    4 |     indata.into_iter().chunks(2).into_iter().flatten()
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------------
      |     |
      |     creates a temporary which is freed while still in use
      |     argument requires that borrow lasts for `'static`
    5 | }
      | - temporary value is freed at the end of this statement
    

    The reason for this error is the fact that IntoChunks is not iterable by value - only by reference; the second .into_iter() is called not on IntoChunks itself, but on a reference to it.

    Not sure why it was implemented this way - this might warrant a question to itertools's authors, or even a PR to add the owned alternative, if it is deemed feasible.