具有拥有和借用值的`if`分支,没有使用let绑定。

5
我有一个if语句,两个分支都需要返回一个HashMap。但是,在其中一个分支中,我有一个拥有所有权的HashMap,而在另一个分支中,我从某个结构体的引用中访问一个HashMap。目前,我被迫在if块外面定义一个未绑定的let变量来保存第一个分支中的拥有所有权的map(否则它会在if块结束时被释放,并且引用将无效)。
struct Descriptor {
    env: HashMap<String, String>
}

fn merge_env(_a: &HashMap<..>, _b: &HashMap<..>) -> HashMap<String, String> {
    todo!()
}

fn example(d1: &Descriptor, d2: &Descriptor, feature_on: bool) {
    // Can I somehow avoid having to declare this?
    let holder;
    let env = if feature_on {
        holder = merge_env(&d1.env, &d2.env);
        &holder
    } else {
        &d1.env
    };
    read_env(&env);
}

fn read_env(_env: &HashMap<String, String>) { todo!() }

Playground

有没有避免未绑定let的方法?也许有个可以保存所有权和借用值的包装器(wrapper)?另外,当前的方式是否符合惯用法?


1
可能可以使用 std::borrow::Cow,但在我看来,变量已经很好了。 - Colonel Thirty Two
2个回答

8
变量在 Rust 中有作用域。一旦它们的作用域结束,它们就会被释放。因此,如果您在 if 中声明您的 holder,如下所示:
if feature_on {
        let holder = merge_env(&d1.env, &d2.env);
        &holder
} 

// holder has already been dropped at that point. It would be "use after free" error to use it here

这个变量在if块之外使用是不可能的,因为它会在那一点上被丢弃。因此,您必须确保所拥有的对象的生命周期足够长。

可能的解决方案之一就是您已经完成的操作 - 将所有权变量移动到if之前,您已经保证了持有者实例不会被释放,从而避免了使用后释放错误。

另一个解决方案是使用枚举类型std::borrow::Cow,它有两个变种 - Cow::Borrowed - 用于保存引用和Cow::Owned - 用于保存实际实例。这完美地适合您的用例:

fn example(d1: &Descriptor, d2: &Descriptor, feature_on: bool) {
    let env =  if feature_on {
        Cow::Owned(merge_env(&d1.env, &d2.env))
    } else {
        Cow::Borrowed(&d1.env)
    };
    read_env(&env);
}

啊,不知道 Cow 可以这样做。谢谢! - ᴘᴀɴᴀʏɪᴏᴛɪs

2
你可以将holder重构为一个Option,并将None情况变成当前的else分支。然后,第二个变量可以是对该Option或参数的引用(类型注释不需要,只是为了解释):
fn example(d1: &Descriptor, d2: &Descriptor, feature_on: bool) {
    let holder: Option<HashMap<_, _>> = feature_on.then(|| merge_env(&d1.env, &d2.env));
    let env: &HashMap<_, _> = holder.as_ref().unwrap_or(&d1.env);
    read_env(env);
}

如果你真的想要摆脱临时变量,你可以内联所有这些内容:

fn example(d1: &Descriptor, d2: &Descriptor, feature_on: bool) {
    read_env(
        feature_on
            .then(|| merge_env(&d1.env, &d2.env))
            .as_ref()
            .unwrap_or(&d1.env),
    );
}

考虑到“这是否符合惯用语”的问题:我认为,你目前的代码也完全没问题。


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