ветвь » если` с собственной и заемной стоимостью без привязки к let

#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),
    );
}
 

Учитывая «является ли это идиоматичным»: я бы сказал, что ваш текущий код тоже абсолютно хорош.