Шаблоны и Futures


#1

Здравствуйте!
Продолжаю осваивать 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/


#2

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


#3

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

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

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

#4
  1. Да, код в процессе переписывания, до этого пытался извлечь location без канала - не получилось.
  2. Первый раз вижу такую запись

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


#5

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


#6

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

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


#7

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

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

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

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


#8

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


#9

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

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


#10

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


#11

Но исполняться-то в недрах 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)


#12

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