`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!() }
有没有办法避免未绑定的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),
);
}
考虑到“这是惯用的”:我会说,您当前的代码也绝对没问题。
我有这个 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!() }
有没有办法避免未绑定的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),
);
}
考虑到“这是惯用的”:我会说,您当前的代码也绝对没问题。