Arraigning a Statement, vol. 1

11 March 2019

This time in our “Mutating Rust” series, we want to tackle the most complex mutation so far: Statement removal. Now why do I think this is complex? It’s just removing the statement (or, as we bake our mutations into the code, activating at runtime, putting it behind an if ), right?

Let’s take a very simple example:

a (); b ();

We want to be able to omit running the a() or the b() , depending on which mutation is active. And in this simple example, it would easily work. Our mutated code might look like:

if ! :: mutagen :: now ( 22 ) { a (); } if ! :: mutagen :: now ( 23 ) { b (); }

However, not all Rust code is that simple. The first obvious complication is that statements may include let bindings, for example:

let x = 1 ; println! ( "{}" , x );

Trying the same simple logic would lead to the following code:

if ! :: mutagen :: now ( 22 ) { let x = 1 ; } if ! :: mutagen :: now ( 23 ) { println! ( "{}" , x ); }

This leads to a compiler error, because the let binding is no longer in scope of the println!(..) statement. Ok, so we need to keep track of locals. Since Rust allows, nay, encourages shadowing, we have to keep all let statements, because they could change types, e.g. the following compiles (though it will warn because the first x is unused):

let x = "Hi!" ; let x = "Ho!" ; println! ( "{}" , x );

Ok, but let bindings only work within the same scope, so we at least don’t need to care for nested expressions? Unfortunately for us, Rust allows deferred initialization, as in the following example:

let x ; { x = 1 ; } // Voilà, nested block! println! ( "{}" , x );

Well, at least we know all statements always only apply to code after the original statement? There is one counter-example here, too:

println! ( "{}" , X ); const X : & str = "Hi!" ;

(Update: The original version stated that static obeyed ordering, but this was tested against some Rust 1.2x and it appears that resolve has changed in the meantime. Bummer)

So we have to filter static and const item statements from removal, but that’s about it. Luckily for us, both bindings for const and static obey scope boundaries, so we need not fear nested statements in this vein:

// println!("{}", X); // ← Error: X not in scope { const X: &str = "Ouch"; }

So for a minimum viable product we can remove free function-call statements, unless the arguments contain any assignment or the return value is the return value of the block. Not much, but a start. Stay tuned for the next entry in this series!