Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refactor out repeated manipulations of local mutable variables?

Tags:

rust

Suppose I have code like this.

fn main() {
    let mut x = 123;
    let mut y = 456;
    let mut z = 789;

    x += y; y *= z; z %= x; // random operations
    x = 1;
    x += y; y *= z; z %= x;
    z = z & y;
    x += y; y *= z; z %= x;
    println!("{} {} {}", x, y, z);
}

Quite contrived, but hopefully the idea is clear --- I have these local variables that I manipulate in a certain complex way more than once.

Obviously I'd like to avoid the duplication and refactor out that manipulation. At the same time, there are so many variables involved that I'd prefer not to pass all of them as mutable reference arguments to an outside utility function.

If this were, say, C++, I might stash the variables in a global scope and define another function, or in more recent versions, use a capturing lambda. I tried to do the corresponding thing in Rust with a closure:

fn main() {
    let mut x = 123;
    let mut y = 456;
    let mut z = 789;

    let f = || {
        x += y; y *= z; z %= x; // random operations
    };

    f();
    x = 1;
    f();
    z = z & y;
    f();
    println!("{} {} {}", x, y, z);
}

But this doesn't compile, because Rust tells me I borrowed x, y, z to f. It seems the very existence of f means I can't use x, y, z in main anymore. This Rust error would make sense if I were trying to pass f out of the function or spawn a process with it or something, but I'm not, and it's evidently safe if I "manually inline" the function as in the first version.

Is there a neat Rust way to do this?

By the way, I found this question and I see that I can avoid an error in the last println! by declaring f inside a block that terminates before that statement, but that doesn't help me if I want to mutate the variables in between calls to f.

like image 242
betaveros Avatar asked Feb 13 '23 14:02

betaveros


2 Answers

One of the simpler ways of doing this is with a macro:

macro_rules! f {
    ($x:ident, $y:ident, $z:ident) => {{
        $x += $y;
        $y *= $z;
        $z %= $x;
    }}
}

This can be invoked with f!(x, y, z). It’s not much nicer than writing a function which takes three &mut int arguments, however.

Now in this specific case, because x, y and z are in scope, provided you write the macro after them you can use x, y and z without needing to pass them through the macro call (recall that Rust’s are hygienic macros). That is, you can write

macro_rules! f {
    () => {{
        x += y;
        y *= z;
        z %= x;
    }}
}

Here’s the code in full:

#![feature(macro_rules)]

fn main() {
    let mut x = 123i;
    let mut y = 456i;
    let mut z = 789i;

    macro_rules! f {
        () => {{
            x += y;
            y *= z;
            z %= x;
        }}
    }

    f!()
    x = 1;
    f!()
    z = z & y;
    f!()
    println!("{} {} {}", x, y, z);
}

In the playpen: http://is.gd/sCaYMS

like image 101
Chris Morgan Avatar answered May 18 '23 17:05

Chris Morgan


One solution is to use a Cell. The contents of a Cell are mutable regardless of the Cell's mutability. It will make the code uglier but at least it compiles. Performance shouldn't be affected as far as I know.

use std::cell::Cell;

fn main() {
    let x = Cell::new(123i);
    let y = Cell::new(456i);
    let z = Cell::new(789i);

    let f = || {
        x.set(x.get() + y.get());
        y.set(y.get() * z.get());
        z.set(z.get() % x.get());
    };

    f();
    x.set(1);
    f();
    z.set(z.get() & x.get());
    f();
    println!("{} {} {}", x, y, z);
}

Probably a better solution is to abstract the data and the functions operating on them as struct.

#[deriving(Show)]
struct Data {
    x: int,
    y: int,
    z: int
}

impl Data {
    fn f(&mut self) {
        self.x += self.y; 
        self.y *= self.z;
        self.z %= self.x;
    }
}


fn main() {
    let mut data = Data { x: 123, y: 456, z: 789 };

    data.f();
    data.x = 1;
    data.f();
    data.z = data.z & data.x;
    data.f();
    println!("{}", data);
}
like image 23
A.B. Avatar answered May 18 '23 17:05

A.B.