Может ли совпадение в Result здесь быть заменено на map_err и "?" - PullRequest
2 голосов
/ 11 января 2020

У меня есть некоторый код, который выглядит следующим образом (значительно упрощенная версия). Функция принимает два аргумента функции типа LoadClient и CheckApproval и возвращает либо ошибку, либо строку.

pub struct Client {
    pub id: String,
}

pub enum MyErr {
    RequiresApproval(Client, String),
    LoadFailed,
}

pub fn authorize<LoadClient, CheckApproval>(load_client: LoadClient, check_approval: CheckApproval) -> Result<String, MyErr> 
where
   LoadClient: FnOnce(String) -> Result<Client, String>,
   CheckApproval: for<'a> FnOnce(&'a Client, &str) -> Result<&'a str, ()>,
{
    let client = load_client("hello".to_string()).map_err(|_| MyErr::LoadFailed)?;
    let permission = "something";

    // This doesn't compile
    // let authorized = check_approval(&client, permission).map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
    // Ok(authorized.to_string())

    // This version does
    match check_approval(&client, permission) {
        Err(_) => Err(MyErr::RequiresApproval(client, permission.to_string())),
        Ok(authorized) => Ok(authorized.to_string()),
    }
}

Я хотел бы использовать ? с вызовом check_approval (как закомментированный код показывает) для более простого кода и во избежание дополнительной вложенности - ветвь Ok в конечном совпадении на самом деле является гораздо более длинным блоком.

К сожалению, это не компилируется:

error[E0505]: cannot move out of `client` because it is borrowed
  --> src/lib.rs:19:66
   |
19 |     let authorized = check_approval(&client, permission).map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
   |                                     -------              ------- ^^^                         ------ move occurs due to use in closure
   |                                     |                    |       |
   |                                     |                    |       move out of `client` occurs here
   |                                     |                    borrow later used by call
   |                                     borrow of `client` occurs here

Они кажутся похожими (на мой неподготовленный глаз). Разве заимствованная ссылка на client не была возвращена ко времени вызова map_err?

Мой главный вопрос: есть ли способ обойти это и написать код, не используя match?

ржавчина детская игровая площадка .

1 Ответ

1 голос
/ 12 января 2020

Хотя ваши две версии кода семантически эквивалентны, для компилятора они на самом деле совершенно разные.

Отказавший вызов вызывает Result::map_err() с замыканием, которое фиксирует значение client. То есть client перемещается в замыкание, но оно заимствуется при вызове check_approval(). И здесь кроется ошибка, заимствованное значение не может быть перемещено.

Вы можете подумать, что этот заем должен завершиться sh, когда функция вернется, но это не так из-за ее типа возврата Result<&'a str, ()>, будучи 'a точно временем жизни этого заимствования. Займ client продлевается до тех пор, пока существует 'a. И именно поэтому ваша вторая версия работает: когда вы соответствуете Result, 'a не распространяется на Err(()) ветвь, только на Ok(&'a str), поэтому Err(()) может свободно перемещаться client .

Есть ли способ обойти это и написать код без использования match?

Ну, вы звоните authorized.to_string() в возвращенном &'a str и преобразование его в принадлежащий String. Итак, если вы можете изменить ограничение CheckApproval на:

CheckApproval: FnOnce(&Client, &str) -> Result<String, ()>,

, проблема просто исчезнет.

Если вы не можете это изменить, другой вариант - сделать to_string() перед перемещение client в закрытие, завершение заимствования до того, как он может причинить вред:

let authorized = check_approval(&client, permission)
    .map(|a| a.to_string())
    .map_err(|_| MyErr::RequiresApproval(client, permission.to_string()))?;
Ok(authorized)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...