I am working with machine-generating Rust code and came across a particularly gnarly case of use-after-move in a piece of code that was naively produced by the generation process.
In short, I am processing a data structure (laid out in binary but that isn't very important for the particular problem-at-hand), that could be one of several distinct formats that are distinguished by parse-failure that falls through into parsing the next alternative in a top-level method. Each individual piece of a prospective data-structure might either get its own parsing function, or an anonymous thunk that is immediately called, to allow things like try-expressions to be caught by the right bounding scope and not short-circuit the entire function body, just the field that might or might not succeed a parse.
I recently encountered a snag when processing a data format with an optional field followed by a dependently-optional (Some if Some, None if None) computed field based on the prior.
In the model of the generated code, this uses std::option::Option.
Heavily simplified microcosm of what is going wrong:
fn main() {
let y = Some(vec![1, 2, 3]);
let x = (|| match y { Some(z) => Some(z.clone()), None => None } )();
println!("{:?}, {:?}", y, x);
}
Attempting to compile this yields a use-after-move error, since y is implicitly captured as an owned value (as we have match y and not match y.clone() or match &y, both of which would solve the problem) by the closure used to evaluate x, and later used again after x is computed.
Despite the naive example provided, the actual code that is being produced cannot be beta-reduced, and inserting a & or .clone() to the scrutinee y in the closure-match is non-trivial because the code is being machine generated according to rules that apply to a lot of other cases, which can easily break in either case.
Is there a good way to solve this 'use-after-move' problem stemming from destructing Some?