user1244932
user1244932

Reputation: 8092

How do I perform an operation only when two variables are not None without copying them?

I want to perform an operation if and only if a and b are not None. I do not want create a copy of complex structures, which is why struct X does not implement Clone.

use std::sync::{Arc, Mutex};
use std::cell::RefCell;

#[derive(Debug)]
struct X {
    d: u32,
}

struct Foo {
    a: Option<X>,
    b: Option<u32>,
    c: u32,
}

fn main() {
    let smart_ptr = Arc::new(Mutex::new(RefCell::new(Foo {
        a: Some(X { d: 1 }),
        b: Some(2),
        c: 3,
    })));

    {
        let lock = smart_ptr.lock().unwrap();
        let foo = lock.borrow();
        if let (Some(ref a), Some(b)) = (foo.a, foo.b) {
            println!("a: {:?}, b: {}", a, b);
        }
    }
}

If I try to compile this code, I get:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:25:42
   |
25 |         if let (Some(ref a), Some(b)) = (foo.a, foo.b) {
   |                                          ^^^ cannot move out of borrowed content

How should I fix the if statement to get what I want without compilation errors?

Upvotes: 0

Views: 155

Answers (3)

hansaplast
hansaplast

Reputation: 11573

You need to tell the if let statement to only reference a and b to foo.a and foo.b, otherwise if let would move some parts out of the borrowed value. This is forbidden for two reasons:

  1. The reference is immutable, so it can not be changed.
  2. References are always guaranteed to point to completely valid objects. If you could move foo.a into a, "stealing away" the struct from your lender, then the value residing in foo.a would no longer be valid, making foo also invalid.

One solution would be to duplicate the values Foo::a and Foo::b via clone() which is - as you noted - unneeded and performance wise not optimal, especially since it would mean a deep copy.

I got it working without copying:

if let (&Some(ref a), &Some(b)) = (&foo.a, &foo.b) {
    println!("a: {:?}, b: {}", a, b);
}

Upvotes: 4

aSpex
aSpex

Reputation: 5216

You can destructure Foo instead of creating a tuple:

if let Foo { a: Some(ref a), b: Some(b), .. } = *foo {
    println!("a: {:?}, b: {}", a, b);
}

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 430851

Using a smaller example:

struct Foo {
    a: Option<String>,
    b: Option<String>,
}

fn main() {
    let foo = &Foo {
        a: Some("hi".into()),
        b: Some("world".into()),
    };

    if let (Some(a), Some(b)) = (foo.a, foo.b) {
        println!("a: {}, b: {}", a, b);
    }
}

You can use Option::as_ref to perform the same type of matching on a reference as the previous answer:

if let (Some(a), Some(b)) = (foo.a.as_ref(), foo.b.as_ref()) {
    println!("a: {}, b: {}", a, b);
}

Upvotes: 6

Related Questions