У меня есть struct
, Thing
, который реализует Clone
, но не Copy
, и у него есть два метода: один для выполнения работы, а другой для получения «потомка» Thing
от существующего «родителя» Thing
. Не у каждого Thing
есть ребенок, поэтому иногда детское происхождение возвращается None
.
Вот API:
#[derive(Clone)]
struct Thing;
impl Thing {
fn do_work(&self) {}
// This is considered expensive
fn try_get_child(&self) -> Option<Thing> {
Some(Thing)
}
}
Я хотел бы реализовать функцию, описанную ниже:
/// If `b` is Some then do work on the Thing inside
/// Otherwise, try to get a child from `a` and do work on that child
/// If both `b` and `a.try_get_child()` are None, do nothing.
fn f(a: Thing, b: Option<&Thing>) {
b.or_else(|| a.try_get_child().as_ref()).map(Thing::do_work);
}
Но эта реализация вызывает ошибку компиляции:
cannot return value referencing temporary value
Кажется, я понимаю, что эта ошибка возникает, потому что ни замыкания, ни функции не могут возвращать ссылки на значения, созданные внутри их тел, поскольку такие значения отбрасываются при возврате выполнения из функции или замыкания. Но каков идиоматический способ реализации fn f
, чтобы дочерний вывод выполнялся только в случае необходимости? Возможно ли использовать методы Option
?
Используя вспомогательные методы Option
, если вы хотите дублировать код (конечно, вы можете извлечь его в функции):
fn f(a: Thing, b: Option<&Thing>) {
b.map(Thing::do_work).or_else(|| a.try_get_child().as_ref().map(Thing::do_work));
}
Без дублирования кода (или если возвращаемое значение do_work()
заимствовано из Thing
), используя хитрый трюк для использования возможно-инициализированной переменной:
fn f(a: Thing, b: Option<&Thing>) {
let a_child;
let value = match b {
Some(_) => b,
None => {
a_child = a.try_get_child();
a_child.as_ref()
}
};
value.map(Thing::do_work);
}
Вот что я получил
fn f(a: Thing, b: Option<&Thing>) {
// Option 1
// verbose `if let` solution
if let Some(t) = b {
t.do_work()
} else if let Some(t) = a.try_get_child().as_ref() {
t.do_work()
}
// Option 2
// Need to compute `a.try_get_child()` even b is `Some(_)`
// If `try_get_child` is not expensive, this look quite good i guess
match (b, a.try_get_child().as_ref()) {
(Some(t), _) | (None, Some(t)) => t.do_work(),
_ => {}
}
// Option 3
// One-liner but still need to compute `a.try_get_child()` whether or not b is `None`
b.map_or(a.try_get_child().as_ref(), |_| b)
.map(Thing::do_work);
}
так как это английское слово, в следующий раз используйте Foo и Bar