Значения перемещения ржавчины из вложенной опции

Есть ли простой/читабельный способ переместить несколько значений из вложенной опции без клонирования (поскольку копирование векторов обходится дорого)?

Рассмотрим такой код

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),
    };
    
}

Это, очевидно, не удается

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
     |

Я мог бы одновременно переместить (c,d) из a.b по отдельности, а затем перейти из них в Result, но для еще более крупных (более глубоких и разветвленных) вложенных структур это становится совершенно нечитаемым.

Причина, по которой мне это нужно: я хотел бы «сгладить» сообщение protobuf (гигантская структура Option), отфильтровать некоторые поля и преобразовать некоторые значения, не копируя все.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
0
62
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Option вспомогательные методы здесь не подойдут, так как они перемещают всё целиком.

Это можно сделать с помощью match, но гораздо приятнее использовать немедленно вызываемые замыкания (или try блоки):


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

Вы можете использовать связку Option::as_mut, а затем Option::take в конце.

let mut a = a;
let result = Result {
    v11: a
        .as_mut()
        .and_then(|a| a.b.as_mut())
        .and_then(|b| b.c.as_mut())
        .and_then(|c| c.v1.take()),
    v22: a
        .as_mut()
        .and_then(|a| a.b.as_mut())
        .and_then(|b| b.d.as_mut())
        .and_then(|d| d.v2.take()),
};

Вы также можете рассмотреть сопоставление с образцом, хотя в этом случае оно не очень читабельно.

let result = Result {
    v11: if let Some(A {
        b: Some(B {
            c: Some(C { v1, .. }),
            ..
        }),
        ..
    }) = &mut a
    {
        v1.take()
    } else {
        None
    },
    v22: if let Some(A {
        b: Some(B {
            d: Some(C { v2, .. }),
            ..
        }),
        ..
    }) = &mut a
    {
        v2.take()
    } else {
        None
    },
};
Ответ принят как подходящий

Я предлагаю двухэтапный подход:

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),
};

Он извлекает значения c и d из цепочки a.b одновременно (с None, если они не существуют), чтобы затем использовать их отдельно для заполнения Result.

Или другая формулировка, которая делает последние and_then внутри 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 };

Я хотел избежать двухэтапного подхода (поскольку его становится довольно трудно читать, когда вы на самом деле перемещаете 7 значений, а не 2 или 3), но, похоже, он единственный, который разумно работает.

Rasty 11.07.2024 18:55

Другие вопросы по теме