How to swap two variables?

2020-02-10 02:30发布

问题:

What is the closest equivalent Rust code to this Python code?

a, b = 1, 2
a, b = b, a + b

I am trying to write an iterative Fibonacci function. I have Python code I want to convert to Rust. Everything is fine, except for the swap part.

def fibonacci(n):
    if n < 2:
        return n
    fibPrev = 1
    fib = 1
    for num in range(2, n):
        fibPrev, fib = fib, fib + fibPrev
    return fib

回答1:

When swapping variables, the most likely thing you want is to create new bindings for a and b.

fn main() {
    let (a, b) = (1, 2);
    let (b, a) = (a, a + b);
}

However, in your actual case, there isn't a nice solution. When you do as above, you always create new bindings for a and b, but you want to modify the existing bindings. One solution I know of is to use a temporary:

fn fibonacci(n: u64) -> u64 {
    if n < 2 {
        return n;
    }
    let mut fib_prev = 1;
    let mut fib = 1;
    for _ in 2..n {
        let next = fib + fib_prev;
        fib_prev = fib;
        fib = next;
    }
    fib
}

You could also make it so that you mutate the tuple:

fn fibonacci(n: u64) -> u64 {
    if n < 2 {
        return n;
    }
    let mut fib = (1, 1);
    for _ in 2..n {
        fib = (fib.1, fib.0 + fib.1);
    }
    fib.1
}

You may also be interested in swapping the contents of two pieces of memory. 99+% of the time, you want to re-bind the variables, but a very small amount of time you want to change things "in place":

fn main() {
    let (mut a, mut b) = (1, 2);
    std::mem::swap(&mut a, &mut b);

    println!("{:?}", (a, b));
}

Note that it's not concise to do this swap and add the values together in one step.

See also:

  • Can I destructure a tuple without binding the result to a new variable in a let/match/for statement?


回答2:

In addition, a better way to implement the Fibonacci sequence in Rust is using the Iterator trait:

// Iterator data structure
struct FibIter(u32, u32);

// Iterator initialization function
fn fib() -> FibIter {
    FibIter(0u32, 1u32)
}

// Iterator trait implementation
impl Iterator for FibIter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        *self = FibIter(self.1, self.1 + self.0);
        Some(self.0)
    }
}

fn main() {
    println!("{:?}", fib().take(15).collect::<Vec<_>>());
}

See The Rust Programming Language chapter on iterators.



回答3:

I'm surprised none of the answers mention the XOR-swap:

fibPrev ^= fib;
fib ^= fibPrev;
fibPrev ^= fib;

This is a well-known way to swap two values without using a temporary variable or risking integer overflow.

Note that with modern optimizing compilers, in most cases it's better from performance standpoint to just use a temporary variable (see link in the comment by trentcl). There are, however, some use cases for the XOR-swap, the linked Wikipedia article provides a decent overview of its pros and cons.

Interestingly enough, if you were to implement a XOR-swap as a function in rust, you don't need to check if you're trying to swap a variable with itself (in which case you'll end up with it being set to zero due to the properties of a XOR operation). Compare swap implementations in rust:

fn swap(a: &mut i32, b: &mut i32) {
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

and C++:

void swap(int& a, int& b) {
    if (&a == &b) return; // Avoid swapping the variable with itself
    a ^= b;
    b ^= a;
    a ^= b;
}

If you try to call swap like this in rust:

let mut a = 42;
swap(&mut a, &mut a);

You will get a compilation error:

error[E0499]: cannot borrow `a` as mutable more than once at a time
  --> src/main.rs:27:18
   |
27 |     swap(&mut a, &mut a);
   |     ---- ------  ^^^^^^ second mutable borrow occurs here
   |     |    |
   |     |    first mutable borrow occurs here
   |     first borrow later used by call


标签: rust