Search code examples
rust

Rust moving values from nested option


Is there an easy/readable way to move multiple values from nested option without cloning (because copying vectors is costly)?

Consider code like this

struct A {
    pub b: Option<B>
}

struct B {
    pub c: Option<C>,
    pub d: Option<C>,
    pub e: Option<C>,
}

struct C {
    pub v1: Option<Vec<i64>>,
    pub v2: Option<Vec<i64>>,
}

struct Result {
    pub v11: Option<Vec<i64>>,
    pub v22: Option<Vec<i64>>,
}

fn main() {
    let a= Some(A {
        b: Some(B {
            c: Some(C {
                v1: Some(vec![1, 2, 3]),
                v2: Some(vec![4, 5, 6]),
            }),
            d: Some(C {
                v1: Some(vec![7, 8, 9]),
                v2: Some(vec![10, 11, 12]),
            }),
            e: Some(C {
                v1: Some(vec![13, 14, 15]),
                v2: Some(vec![16, 17, 18]),
            }),
        }),
    });

    // Construct Result such that v11 = v1 from c and v22 = v2 from d if all fields are Some
    let result = Result {
        v11: a.and_then(|a| a.b).and_then(|b| b.c).and_then(|c| c.v1),
        v22: a.and_then(|a| a.b).and_then(|b| b.d).and_then(|d| d.v2),
    };
    
}

This obviously fails on

error[E0382]: use of moved value: `a`
    --> src/main.rs:42:14
     |
22   |     let a= Some(A {
     |         - move occurs because `a` has type `Option<A>`, which does not implement the `Copy` trait
...
41   |         v11: a.and_then(|a| a.b).and_then(|b| b.c).and_then(|c| c.v1),
     |              - ----------------- `a` moved due to this method call
     |              |
     |              help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
42   |         v22: a.and_then(|a| a.b).and_then(|b| b.d).and_then(|d| d.v2),
     |              ^ value used here after move
     |

I could simultaneously move (c,d) from a.b separately and then move from those into the Result, but for even larger (deeper, more branched) nested structures this gets absolutely unreadable.

Reason why I need this: I would like to "flatten" a protobuf message (giant structure of Options), filter out some fields and transform some values without copying everything.


Solution

  • I propose a two-step approach:

    let (c, d) = a
        .and_then(|a| a.b)
        .map(|b| (b.c, b.d))
        .unwrap_or((None, None));
    
    let result = Result {
        v11: c.and_then(|c| c.v1),
        v22: d.and_then(|d| d.v2),
    };
    

    It pulls the c and d values from the a.b chain at the same time (with None if they don't exist) so it can then use them separately for filling in the Result.

    Or a different formulation that does the last and_thens inside the map:

    let (v11, v22) = a
        .and_then(|a| a.b)
        .map(|b| (
            b.c.and_then(|c| c.v1),
            b.d.and_then(|d| d.v2),
        ))
        .unwrap_or((None, None));
    
    let result = Result { v11, v22 };