First commit.

This commit is contained in:
Nathan Vegdahl 2024-02-07 16:41:05 +01:00
commit 281d75ced9
4 changed files with 126 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "async_tests"
version = "0.1.0"

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "async_tests"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

110
src/main.rs Normal file
View File

@ -0,0 +1,110 @@
//! Experiments in manually polling and yielding from Futures.
//!
//! The idea here is that Futures are a really convenient way to write state
//! machines that progressively do computations. However, the whole async
//! ecosystem so far seems to be built on the following assumptions:
//!
//! 1. async code is primarily meant for yielding on IO, not for doing
//! computations in a controlled iterative way.
//! 2. Polling async code should be done by "executors" rather than manually.
//!
//! Below is an example of using async outside of those assumptions. In
//! this case, we have async code that never does any IO, and is purely
//! doing computations. But it does those computations *progressively*, with
//! yield points to allow other code to run, and manual polling to move the
//! computation along as desired.
use std::{
future::Future,
pin::{pin, Pin},
task::Context,
task::Poll,
};
fn main() {
// These are just examples of using the APIs further down in the file.
// Polling an async function.
let mut b = pin!(foo(8));
loop {
match poll(&mut b) {
Poll::Ready(v) => {
println!("{}", v);
break;
}
Poll::Pending => println!("Not yet!"),
}
}
// Polling an async block that captures things.
let mut x = 5;
let y = &mut x;
let mut f = pin!(async move {
for _ in 0..10 {
*y += 1;
pause().await;
}
});
loop {
match poll(&mut f) {
Poll::Ready(_) => {
println!("{}", x);
break;
}
Poll::Pending => println!("Not yet!"),
}
}
}
async fn foo(a: i32) -> i32 {
let mut b = 26;
for _ in 0..10 {
b += a;
pause().await;
}
b
}
//-------------------------------------------------------------
/// If awaited, will yield control to the executor immediately.
pub fn pause() -> Pause {
Pause { first_time: true }
}
/// A future that simply yields to the executor.
pub struct Pause {
first_time: bool,
}
impl Future for Pause {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.first_time {
self.as_mut().first_time = false;
cx.waker().wake_by_ref();
Poll::Pending
} else {
Poll::Ready(())
}
}
}
//-------------------------------------------------------------
/// Polls a future once, without a waker.
pub fn poll<F: Future + Unpin>(future: F) -> Poll<F::Output> {
use std::task::{RawWaker, RawWakerVTable, Waker};
// TODO: replace with `std::task::Waker::noop()` if/when it
// lands on stable Rust.
let noop_waker = {
const VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RAW, |_| {}, |_| {}, |_| {});
const RAW: RawWaker = RawWaker::new(std::ptr::null(), &VTABLE);
unsafe { Waker::from_raw(RAW) }
};
let mut context = Context::from_waker(&noop_waker);
pin!(future).poll(&mut context)
}