#rust #ownership
Вопрос:
У меня есть это if
утверждение, в котором обе ветви должны возвращать a amp;HashMap<..>
. В одной из ветвей, хотя у меня есть собственный HashMap
, а в другой я получаю доступ к одному из них по ссылке на какую-то структуру. В настоящее время я вынужден определить несвязанную let
переменную вне if
блока для хранения принадлежащей карты из первой ветви (в противном случае она была бы освобождена в конце if
блока, и ссылка была бы недействительной).
struct Descriptor {
env: HashMap<String, String>
}
fn merge_env(_a: amp;HashMap<..>, _b: amp;HashMap<..>) -> HashMap<String, String> {
todo!()
}
fn example(d1: amp;Descriptor, d2: amp;Descriptor, feature_on: bool) {
// Can I somehow avoid having to declare this?
let holder;
let env = if feature_on {
holder = merge_env(amp;d1.env, amp;d2.env);
amp;holder
} else {
amp;d1.env
};
read_env(amp;env);
}
fn read_env(_env: amp;HashMap<String, String>) { todo!() }
Есть ли способ сделать это, избегая несвязанного let
? Может быть, какая-нибудь обертка, которая может содержать как принадлежащую, так и заимствованную стоимость? Кроме того, является ли нынешний способ идиоматичным?
Комментарии:
1. Возможно, можно было бы использовать
std::borrow::Cow
, но ИМО переменная в порядке.
Ответ №1:
Переменные имеют область действия в rust. Как только их область действия заканчивается, они освобождаются от распределения. Таким образом, если вы заявите о своем holder
внутреннем if
подобном:
if feature_on {
let holder = merge_env(amp;d1.env, amp;d2.env);
amp;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: amp;Descriptor, d2: amp;Descriptor, feature_on: bool) {
let env = if feature_on {
Cow::Owned(merge_env(amp;d1.env, amp;d2.env))
} else {
Cow::Borrowed(amp;d1.env)
};
read_env(amp;env);
}
Комментарии:
1. Ах, я не знал
Cow
, что смогу это сделать. Спасибо!
Ответ №2:
Вы можете рефакторинговать holder
, чтобы быть Option
и иметь None
дело с тем, что в настоящее время является вашей другой ветвью. Затем вторая переменная может быть ссылкой на это Option
или на аргумент (аннотации типов не нужны, просто для объяснения).:
fn example(d1: amp;Descriptor, d2: amp;Descriptor, feature_on: bool) {
let holder: Option<HashMap<_, _>> = feature_on.then(|| merge_env(amp;d1.env, amp;d2.env));
let env: amp;HashMap<_, _> = holder.as_ref().unwrap_or(amp;d1.env);
read_env(env);
}
Если вы действительно хотите избавиться от временной переменной, вы можете встроить все это:
fn example(d1: amp;Descriptor, d2: amp;Descriptor, feature_on: bool) {
read_env(
feature_on
.then(|| merge_env(amp;d1.env, amp;d2.env))
.as_ref()
.unwrap_or(amp;d1.env),
);
}
Учитывая «является ли это идиоматичным»: я бы сказал, что ваш текущий код тоже абсолютно хорош.