使用Option<&T>获取Option::unwrap_or_else行为的简洁方式

3

我想知道是否有一种优雅的解决方案来获取类似于unwrap_or_else在Option<&T>上的代码/行为。我的用例是将可选的引用传递给一个函数,如果没有使用,则创建同一类型的默认值以供使用。这是我代码的简化版本:

#[derive(Debug)]
struct ExpensiveUnclonableThing {}

fn make_the_thing() -> ExpensiveUnclonableThing {
    // making the thing is slow
    // ...
    ExpensiveUnclonableThing {}
}

fn use_the_thing(thing_ref: &ExpensiveUnclonableThing) {
    dbg!(thing_ref);
}

fn use_or_default(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    enum MaybeDefaultedRef<'a> {
        Passed(&'a ExpensiveUnclonableThing),
        Defaulted(ExpensiveUnclonableThing),
    }
    let thing_md = match thing_ref_opt {
        Some(thing_ref) => MaybeDefaultedRef::Passed(thing_ref),
        None => MaybeDefaultedRef::Defaulted(make_the_thing()),
    };
    let thing_ref = match &thing_md {
        MaybeDefaultedRef::Passed(thing) => thing,
        MaybeDefaultedRef::Defaulted(thing) => thing,
    };
    use_the_thing(thing_ref);
}

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
    use_the_thing(thing_ref);
}

fn main() {
    let thing = make_the_thing();

    use_or_default(Some(&thing));
    use_or_default(None);

    use_or_default_nicer(Some(&thing));
    use_or_default_nicer(None);
}

当unwrap_or_else闭包结束时,该对象会立即丢弃,因此当我尝试这样做时会收到错误提示:无法执行该操作。
error[E0515]: cannot return reference to temporary value
  --> src/main.rs:31:53
   |
31 |     let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
   |                                                     ^----------------
   |                                                     ||
   |                                                     |temporary value created here
   |                                                     returns a reference to data owned by the current function

什么是 Rust 的“惯用方式”来编写 use_or_default?除了创建一个通用的 MaybeDefaultedRef 类型并带有一些便利方法外,还有没有其他方法可以使其看起来类似于 use_or_default_nicer 的实现?如果有更好的方法,我愿意重构整个东西。
2个回答

5

您可以编写以下内容:

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let mut maybe = None;
    let thing_ref = thing_ref_opt.unwrap_or_else(
        || maybe.insert(make_the_thing())
    );
    use_the_thing(thing_ref);
}

也就是说,你可以在函数外部保留该值本身,并在必要时进行分配。不幸的是,未初始化的值无法被lambda捕获,因此您必须使用Option<ExpensiveUnclonableThing>变量并用None进行初始化。

但是在我的一个真实代码中,我遇到了相同的问题,并编写了一个手动的match

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let maybe;
    let thing_ref = match thing_ref_opt {
        Some(x) => x,
        None => {
            maybe = make_the_thing();
            &maybe
        }
    };
    use_the_thing(thing_ref);
}

在我看来,这种写法更好,尽管代码会略微冗长,因为你不需要使用可变的Option<_>maybe 变量, 也无需进行伪初始化。

有些人在对Option进行match时可能感到有点失败,并认为非成语化,但我并不特别在意。


不错!我也更喜欢第二种解决方案,因为没有可变变量/闭包副作用。 - David Huculak

0

一个简单的if/else也可以,没有必要使事情变得复杂:

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    if let Some(e) = thing_ref_opt {
        use_the_thing(e);
    } else {
        let e = make_the_thing();
        use_the_thing(&e);
    }
}

游乐场


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接