`if` 分支具有拥有和借用的价值而没有 let 绑定

`if` branch with owned and borrowed value without let binding

我有这个 if 声明,其中两个分支都必须 return 一个 &HashMap<..>。在其中一个分支中,虽然我拥有一个 HashMap,而在另一个分支中,我正在从对某个结构的引用中访问一个。目前,我被迫在 if 块之外定义一个未绑定的 let 变量,以保存第一个分支的拥有地图(否则它会在 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?也许一些包装器可以同时持有自有和借用的价值?另外,目前的方式是惯用的吗?

变量在 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 之前移动拥有变量,您已经保证持有者实例不会被释放,从而避免了 use after free 错误.

另一种解决方案是使用枚举 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);
}

您可以将 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),
    );
}

考虑到“这是惯用的”:我会说,您当前的代码也绝对没问题。