Lazily move value once in a loop


#1

I want to init a variable lazily inside a loop (in every iteration I check whether it’s been initialized already, so I know the initialization code runs at most once, but the borrow checker doesn’t believe me).

Is there a type/macro/programming pattern for this in Rust?

Specifically, I’m encoding a gif from a list of frame files. The encoder needs to outlive the loop, but also needs to know the image size up front, but I know image size only inside my loop:


fn write_to<W>(output_writer: W) {
   let mut encoder = None;
   for f in files {
      let image = load_image(f);
      if encoder.is_none() { 
         encoder = Some(Encoder::new(output_writer /*moved!*/, image.width, image.height)));
     }
     encoder.add_frame(image);
  }
}

If I use Option<Encoder> for the encoder, it doesn’t borrow check, because output_writer used to construct the encoder could be moved multiple times in the loop.

I don’t see any other solution than duplicating/unrolling the first iteration of the loop. Is there another solution?


#3

playpen

enum State<W> {
    Uninitialized(W),
    Initialized(Encoder),
}

impl<W> State<W> {
    fn add_frame(self, image: Image) -> Self {
        let mut encoder = match self {
            State::Initialized(encoder) => encoder,
            State::Uninitialized(writer) => {
                Encoder::initialize(writer, image.width, image.height)
            }
        };
        
        encoder.add_frame(image);
        
        State::Initialized(encoder)
    }
}

//------------------------------------------------

fn write_to<W>(output_writer: W)
{

    let mut state = State::Uninitialized(output_writer);

    for () in vec![/* ... */] {
        let image = unimplemented!();
        state = state.add_frame(image);
    }
}

I don’t see any other solution than duplicating/unrolling the first iteration of the loop. Is there another solution?

For a short loop like your code sample (the only duplicated code would be a call to load_image), honestly, I would just unroll the first iteration, because it’s much more direct. But somehow I imagine that your actual code is not so short. :slight_smile:


#4

Moving the input safely using an enum is a great idea. Thank you.


#5

Using Option with its .take() method should also be possible.


#6

Perhaps another option is something like:

files.iter().next().map(|f| Encoder::new(...)).map(|e| files.into_iter().for_each(|f| e.add_frame(f)));