Шаблоны и Futures

Здравствуйте!
Продолжаю осваивать Rust тренируясь на разработке клиента Google OAuth 2.0. Никак в голове не укладываются трёхэтажные шаблоны.
Есть вот такой код

fn retrieve_auth_url() {
    let (sender, receiver) = futures::sync::oneshot::channel::<Url>();
    hyper::rt::run(hyper::rt::lazy(move || {
        retrieve_url(sender)
    }));

    let location = receiver.wait();

    println!("Response: {:?}", location);
}

fn retrieve_url(send: Sender<Url>) -> Client<HttpsConnector<Error>> {
    let uri = Url::parse_with_params(GOOGLE_AUTH_URL, [
        ("client_id", CLIENT_ID),
        ("redirect_uri", "http://[::1]:3000"),
        ("response_type", "code"),
        ("scope", "https://www.googleapis.com/auth/drive")
    ].iter());

    let url = uri.unwrap().as_str().parse::<Uri>().unwrap();

    let https = HttpsConnector::new(1).unwrap();
    let client = Client::builder().build::<_, hyper::Body>(https);

    client.get(url)
        .map(|res| {
            Url::parse(res.headers().get("location").unwrap().to_str().unwrap()).unwrap()
        })
        .map_err(|err| {
            err
        })
}

Каким должен быть тип, возвращаемый функцией retrieve_url? Компилятор на него постоянно ругается, аргументируя тем, что hyper::rt::lazy требует другой тип (я пробовал разные варианты). При этом, если перенести код функции в замыкание - всё работает.
Ощущение, что я что-то упустил с шаблонами.

Заранее спасибо.

PS. А почему тип MapErr, который возвращает функция Client::map_err может выступать в качестве типа Client? Вот пример https://hyper.rs/guides/client/basic/

У них в примере подобная функция возвращает impl Future https://github.com/hyperium/hyper/blob/master/examples/client.rsMapErr его имплементирует, так же как и ResponseFuture и Map)

Здравствуйте!

По коду.
Здесь много чего не совсем понятно/завершено:

  1. Вы создаете канал:
let (sender, receiver) = futures::sync::oneshot::channel::<Url>();

передаете sender:

retrieve_url(sender)

потом ждете ответа:

let location = receiver.wait();

Но внутри функции retrieve_url ничего не отправляете!
2. client.get(url) возвращает футуру (да, вы делаете мапы, но в итоге это все же футура) . Таким образом возвращаемое значение должно быть impl Future:

fn retrieve_url(send: Sender<Url>) -> impl Future<Item = Url, Error = Error> {
  1. hyper::rt::run(<impl Future>) ждет на вход что-либо имплементирующее треит Future (в данном случае hyper::rt::lazy излишен) та что код будет примерно таким:
fn retrieve_auth_url() {
    hyper::rt::run(
        retrieve_url()
            .map(|url| println!("Response: {:?}", location))
            .map_err(|err| eprintln!("Error: {}", err))
    );
}
  1. Да, код в процессе переписывания, до этого пытался извлечь location без канала - не получилось.
  2. Первый раз вижу такую запись

Зачем тут impl? Разве не просто “-> Future<…”

Future это trait, а трейты в расте это что-то на подобии интерфейсов в других языках, это не конкретные типы, а значит у них нет заранее известного(во время компиляции) размера, и их нельзя просто так возвращать (или передавать) из функцию. Запись типа impl Future означат лишь что мы знаем что функция возвращает объект который имплементирует трейт Future. В данном случае конкретный тип объекта буде MapErr<Map<Get …>>> его нельзя записать (как тип возвращаемого значения), т.к. он содержит дженерики с замыканиями, поэтому мы пользуемся impl Future

Спасибо за подробное объяснение!
Ещё один момент не проясните?

А как это та же самая, если MapErr - это не трейт, а структура? Т.е. вообще другой тип.

Дело в том что функции могут возвращать(и принимать тоже) обобщенные значения ака дженерики:

fn my_function<F: Future>(future: F) { // в эту ф-ю можно передать объект любого конкретного типа который имеет имплементацию трейта `Future` 
    ...
}

технически компилятор для каждого конкретного типа создаст копию этой функции (для тех что мы используем конечно)

impl Future это частный случай дженериков для возвращаемого (и не только) значения.

Да, это понятно, но MapErr - это же вполне себе конкретный тип. Как и Client.

А как это та же самая, если MapErr - это не трейт, а структура?

Да конкретный тип другой, но все они имеют имплементацию трейта Future.

Но исполняться-то в недрах Tokio должен именно Client, а не MapErr!

Но исполняться-то в недрах Tokio должен именно Client, а не MapErr!

Не совсем. Токио ничего о Client не знает. Токио нужет любой объект (будь то MapErr или MySuperPuperFuture) который имеет имплементацию трейта Future. Вот пример того как может выглядеть метод run у токио:

pub fn run<F: Future<Item = (), Error = ()>>(f: F) {
   // детали
}

как видим никакого Client тут и впомине нет (Client это объект Hyper для установления связи с удаленным сервером и передачи данных. Client даже не имплементит trait Future, у него есть метод .get который возвращает что-то что имплементирует Future)

1 лайк

Уфф… Вроде понятнее стало.
Большое спасибо!