Impl trait and lifetimes question

For the context: I am implementing a solution for Tom's Data Onion.

In my main function I was trying to iterate over my solutions for various layers of the task using fold to finally get the final answer in the end. Following is the code.

use std::string::FromUtf8Error;
use toms_data_onion::a85::decode;
use toms_data_onion::{find_payload, layer0, layer1, layer2, layer3, layer4, layer5, layer6};

fn main() {
    let layer0 = include_str!("start.txt").to_string();
    let core = unveilers()
        .into_iter()
        .copied()
        .enumerate()
        .fold(layer0, |layer, (idx, unveil)| {
            let payload = find_payload(&layer)
                .expect(&format!("payload not found in layer {idx}"))
                .as_bytes()
                .into_iter()
                .copied();
            let decoded = decode(payload);
            unveil(decoded).expect(&format!("invalid UTF-8 in layer {idx}"))
        });
    println!("{core}");
}

fn unveilers<T: Iterator<Item = u8>>() -> &'static [fn(T) -> Result<String, FromUtf8Error>] {
    &[
        layer0::unveil,
        layer1::unveil,
        layer2::unveil,
        layer3::unveil,
        layer4::unveil,
        layer5::unveil,
        layer6::unveil,
    ]
}

However, borrow checker is not happy with this code:

error[E0597]: `layer` does not live long enough
  --> src/main.rs:12:40
   |
11 |         .fold(layer0, |layer, (idx, unveil)| {
   |                        -----  ------------- lifetime `'1` appears in the type (usize, fn(impl Iterator<Item = u8>) -> Result<String, FromUtf8Error>)
   |                        |
   |                        binding `layer` declared here
12 |             let payload = find_payload(&layer)
   |                                        ^^^^^^ borrowed value does not live long enough
...
17 |             let decoded = decode(payload);
   |                           --------------- argument requires that `layer` is borrowed for `'1`
18 |             unveil(decoded).expect(&format!("invalid UTF-8 in layer {idx}"))
19 |         });
   |         - `layer` dropped here while still borrowed

find_payload is

pub fn find_payload(s: &str) -> Option<&str> {
    Some(&s[s.find("<~")? + 2..s.find("~>")?])
}

And decode is a function declared as pub fn decode<I: IntoIterator<Item = u8>>(it: I) -> impl Iterator<Item = u8>

Could you please help me understand this error?

layer is passed into the closure has a String type. find_payload borrows it, and this returned payload is bound to its lifetime. I am not sure why here the borrow checker concludes that borrowed value does not live long enough. But also later, I understand that since further I mainly operate with iterators, those too capture the lifetime of the object they iterate over. So decoded should also have an implicit lifetime bound to its type. However, unveil has a type fn(impl Iterator<Item = u8>) -> Result<String, FromUtf8Error>, and there is no need for its returned value to have any lifetime limitation. But the compiler seems to be unhappy by the layer being dropped at the end of the closure. But conceptually, there is no need for it to live any longer.

I might be missing something obvious, but please help me express my intention to the compiler in a way that makes sense to it.

Thank you!

I believe the problem is that

fn unveilers<T: Iterator<Item = u8>>() -> 
//  vvvvvvvv     v
    &'static [fn(T) -> Result<String, FromUtf8Error>]
{

implicitly requires T: 'static, but your iterators inside the fold are borrowing the local variable layer (indirectly).

Edit: It's not the 'static, it's the fact that T has to be the same for every iteration through the loop. So all the borrowing iterators would have to be borrowed for the same lifetime, so all the layers would have to be alive at the same time, but they can't be because they get dropped at the end of the closure.

This gets rid of that local borrowing:

            let payload = find_payload(&layer)
                .expect(&format!("payload not found in layer {idx}"))
                .as_bytes()
                .into_iter()
-                .copied();
+                .copied()
+                .collect::<Vec<_>>();

Oh... I think this is the key piece that I was failing to understand. This now makes a lot more sense. I wonder if I can refactor this to work without collecting the bytes into a vector. I understand this is the easiest solution, but now I am curious if it is possible to make it work as I wanted. Is the language expressive enough?

Depends on what you mean by "as [you] wanted". I don't think it's really doable with your function being part of the iterator chain (like Fold), since that's what's forcing the types to be the same.

If you write an iterative loop so that each unveil can be local to the loop, instantiated with distinct T, that should work. Something like this (but be warned I didn't take care to make sure I preserved the logic).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.