Как выполнить асинхронную / ожидающую функцию без использования каких-либо внешних зависимостей? - PullRequest
0 голосов
/ 22 мая 2019

Я пытаюсь создать максимально простой пример, который может заставить async fn hello() в конечном итоге распечатать Hello World!.Это должно происходить без каких-либо внешних зависимостей, таких как tokio, просто Rust и std.Бонусные баллы, если мы сможем сделать это, даже не используя unsafe.

#![feature(async_await)]

async fn hello() {
    println!("Hello, World!");
}

fn main() {
    let task = hello();

    // Something beautiful happens here, and `Hello, World!` is printed on screen.
}
  • Я знаю, async/await по-прежнему ночная функция, и она может измениться в обозримом будущем.
  • Я знаю, что существует множество реализаций Future, я знаю о существовании tokio.
  • Я просто пытаюсь понять, как работают стандартные библиотечные фьючерсы.

Мои беспомощные, неуклюжие усилия

Мое смутное понимание состоит в том, что, во-первых, мне нужно Pin выполнить задание.Итак, я пошел вперед и

let pinned_task = Pin::new(&mut task);

, но

the trait `std::marker::Unpin` is not implemented for `std::future::GenFuture<[static generator@src/main.rs:7:18: 9:2 {}]>`

, поэтому я подумал, конечно, что мне, вероятно, нужно Box, так что я уверен, что он не будет двигатьсявокруг в памяти.Несколько удивительно, что я получаю ту же ошибку.

Что я мог бы получить, так это

let pinned_task = unsafe {
    Pin::new_unchecked(&mut task)
};

, что, очевидно, не то, что я должен делать.Тем не менее, скажем, я взял в свои руки Pin Нед Future.Теперь мне нужно как-то poll().Для этого мне нужен Waker.

Так что я попытался осмотреться, как достать Waker.На doc это выглядит как единственный способ получить Waker с другим new_unchecked, который принимает RawWaker.Оттуда я получил здесь и оттуда здесь , где я просто свернулся калачиком на полу и заплакал.

1 Ответ

2 голосов
/ 22 мая 2019

Эта часть стека фьючерсов не предназначена для , который должен быть реализован многими людьми.Грубая оценка, которую я видел в этом, может быть, будет приблизительно 10 или около того фактических реализаций.

Тем не менее, вы можете заполнить основные аспекты исполнителя, которые чрезвычайно ограничены, следуя необходимым сигнатурам функций:

// Using Nightly from 2019-05-21
#![feature(async_await)]

async fn hello() {
    println!("Hello, World!");
}

fn main() {
    drive_to_completion(hello());
}

use std::{
    future::Future,
    ptr,
    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

fn drive_to_completion<F>(f: F) -> F::Output
where
    F: Future,
{
    let waker = my_waker();
    let mut context = Context::from_waker(&waker);

    let mut t = Box::pin(f);
    let t = t.as_mut();

    loop {
        match t.poll(&mut context) {
            Poll::Ready(v) => return v,
            Poll::Pending => panic!("This executor does not support futures that are not ready"),
        }
    }
}

type WakerData = *const ();

unsafe fn clone(_: WakerData) -> RawWaker {
    my_raw_waker()
}
unsafe fn wake(_: WakerData) {}
unsafe fn wake_by_ref(_: WakerData) {}
unsafe fn drop(_: WakerData) {}

static MY_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);

fn my_raw_waker() -> RawWaker {
    RawWaker::new(ptr::null(), &MY_VTABLE)
}

fn my_waker() -> Waker {
    unsafe { Waker::from_raw(my_raw_waker()) }
}

Начиная с Future::poll, мы видим, что нам нужно Pin недельное будущее и Context.Context создается из Waker, для которого требуется RawWaker.A RawWaker требуется RawWakerVTable.Мы создаем все эти части самыми простыми способами:

  • Поскольку мы не пытаемся поддерживать NotReady дела, нам никогда не нужно ничего делать для этого случая и вместо этого можемпаника.Это также означает, что реализации wake могут не выполняться.

  • Поскольку мы не стремимся быть эффективными, нам не нужно хранить какие-либо данные для нашего стакана, поэтому clone и drop могут быть в основном бездействующими, посколькухорошо.

  • Самый простой способ закрепить будущее - это Box, но это не самая эффективная возможность.


Если вы хотите поддержать NotReady, самое простое расширение - иметь занятый цикл, опрашивающий вечно.Немного более эффективное решение - иметь глобальную переменную, которая указывает, что кто-то вызвал wake, и блокировка на этом становится истинной.

...