#Avoid implicit move semantics for reference-safe match-destructuring within closure

6 messages · Page 1 of 1 (latest)

opal ore
#

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?

rugged violet
#

Can you make the generator generate match y { Some(ref z) => ? That will keep the match from moving y.

opal ore
rugged violet
#

This is in this case equvalent to the effect of match &y, but since you get to pick on the pattern side rather than the scrutinee side it might be usefully different for your generation strategy.

#

Another angle would be match y.clone() {, which in the general case is expensive but might be cheap in the cases you actually care about.

opal ore
#

y.clone() would definitely solve at least this case, but would be hard to prevent from spiking the runtime memory cost of many other match ... sites elsewhere in the generated code. Comparitively, implementing ref z would require a bit of added static-analysis to figure out whether referential binding is appropriate for how the bound variables are used, but certainly easier than selectively eliding the requisite & or clone(), at least in my assessment.