Есть ли простой/читабельный способ переместить несколько значений из вложенной опции без клонирования (поскольку копирование векторов обходится дорого)?
Рассмотрим такой код
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
), отфильтровать некоторые поля и преобразовать некоторые значения, не копируя все.
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), но, похоже, он единственный, который разумно работает.