use core::fmt::Debug;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
pub trait Pipe {
type In;
type Out;
type Err: Debug + 'static;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>>;
}
pub trait SendPipe: Send + Sync + 'static {
type In;
type Out;
type Err: Debug + Send + 'static;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> + Send;
}
pub trait UnpinPipe {
type In;
type Out;
type Err: Debug + 'static;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> + Unpin;
}
pub trait UnpinSendPipe: Send + Sync + 'static {
type In;
type Out;
type Err: Debug + Send + 'static;
fn call(
&self,
input: Self::In,
) -> impl Future<Output = Result<Self::Out, Self::Err>> + Send + Unpin;
}
#[derive(Debug, Clone, Copy)]
pub struct AndThen<First, Second> {
first: First,
second: Second,
}
impl<First, Second> AndThen<First, Second> {
#[must_use]
pub const fn new(first: First, second: Second) -> Self {
Self { first, second }
}
}
impl<First, Second> Pipe for AndThen<First, Second>
where
First: Pipe,
Second: Pipe<In = First::Out>,
Second::Err: From<First::Err>,
{
type In = First::In;
type Out = Second::Out;
type Err = Second::Err;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> {
async move {
let intermediate = self.first.call(input).await?;
self.second.call(intermediate).await
}
}
}
impl<First, Second> SendPipe for AndThen<First, Second>
where
First: SendPipe,
Second: SendPipe<In = First::Out>,
Second::Err: From<First::Err>,
First::In: Send,
{
type In = First::In;
type Out = Second::Out;
type Err = Second::Err;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> + Send {
async move {
let intermediate = self.first.call(input).await?;
self.second.call(intermediate).await
}
}
}
enum AndThenUnpinCall<FirstFut, Next, SecondFut> {
First {
future: FirstFut,
next: Option<Next>,
},
Second {
future: SecondFut,
},
}
fn start_and_then<FirstFut, Intermediate, FirstErr, Next, SecondFut>(
future: FirstFut,
next: Next,
) -> AndThenUnpinCall<FirstFut, Next, SecondFut>
where
FirstFut: Future<Output = Result<Intermediate, FirstErr>>,
Next: FnOnce(Intermediate) -> SecondFut,
{
AndThenUnpinCall::First {
future,
next: Some(next),
}
}
impl<FirstFut, Intermediate, FirstErr, Next, SecondFut, SecondOut, SecondErr> Future
for AndThenUnpinCall<FirstFut, Next, SecondFut>
where
FirstFut: Future<Output = Result<Intermediate, FirstErr>> + Unpin,
Next: FnOnce(Intermediate) -> SecondFut + Unpin,
SecondFut: Future<Output = Result<SecondOut, SecondErr>> + Unpin,
SecondErr: From<FirstErr>,
{
type Output = Result<SecondOut, SecondErr>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
loop {
match this {
AndThenUnpinCall::First { future, next } => {
match Pin::new(future).poll(cx) {
Poll::Ready(Ok(intermediate)) => {
let Some(next) = next.take() else {
return Poll::Pending;
};
*this = AndThenUnpinCall::Second {
future: next(intermediate),
};
}
Poll::Ready(Err(err)) => return Poll::Ready(Err(SecondErr::from(err))),
Poll::Pending => return Poll::Pending,
}
}
AndThenUnpinCall::Second { future } => return Pin::new(future).poll(cx),
}
}
}
}
impl<First, Second> UnpinPipe for AndThen<First, Second>
where
First: UnpinPipe,
Second: UnpinPipe<In = First::Out>,
Second::Err: From<First::Err>,
{
type In = First::In;
type Out = Second::Out;
type Err = Second::Err;
fn call(&self, input: Self::In) -> impl Future<Output = Result<Self::Out, Self::Err>> + Unpin {
let second = &self.second;
start_and_then(self.first.call(input), move |intermediate| {
second.call(intermediate)
})
}
}
impl<First, Second> UnpinSendPipe for AndThen<First, Second>
where
First: UnpinSendPipe,
Second: UnpinSendPipe<In = First::Out>,
Second::Err: From<First::Err>,
{
type In = First::In;
type Out = Second::Out;
type Err = Second::Err;
fn call(
&self,
input: Self::In,
) -> impl Future<Output = Result<Self::Out, Self::Err>> + Send + Unpin {
let second = &self.second;
start_and_then(self.first.call(input), move |intermediate| {
second.call(intermediate)
})
}
}
mod marker_propagation {
use super::AndThen;
use proxima_core::markers::{
AllocFree, Commutative, Deterministic, DropSafe, IdempotentSideEffectFree, IsPure, NoStd,
Reproducible, WithoutFilesystem, WithoutNetwork, WithoutRandom, WithoutSpawn, WithoutTime,
};
impl<First: DropSafe, Second: DropSafe> DropSafe for AndThen<First, Second> {}
impl<First: NoStd, Second: NoStd> NoStd for AndThen<First, Second> {}
impl<First: AllocFree, Second: AllocFree> AllocFree for AndThen<First, Second> {}
impl<First: IsPure, Second: IsPure> IsPure for AndThen<First, Second> {}
impl<First: Deterministic, Second: Deterministic> Deterministic for AndThen<First, Second> {}
impl<First: Reproducible, Second: Reproducible> Reproducible for AndThen<First, Second> {}
impl<First: IdempotentSideEffectFree, Second: IdempotentSideEffectFree> IdempotentSideEffectFree
for AndThen<First, Second>
{
}
impl<First: Commutative, Second: Commutative> Commutative for AndThen<First, Second> {}
impl<First: WithoutFilesystem, Second: WithoutFilesystem> WithoutFilesystem
for AndThen<First, Second>
{
}
impl<First: WithoutNetwork, Second: WithoutNetwork> WithoutNetwork for AndThen<First, Second> {}
impl<First: WithoutSpawn, Second: WithoutSpawn> WithoutSpawn for AndThen<First, Second> {}
impl<First: WithoutTime, Second: WithoutTime> WithoutTime for AndThen<First, Second> {}
impl<First: WithoutRandom, Second: WithoutRandom> WithoutRandom for AndThen<First, Second> {}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::{AndThen, Pipe, SendPipe};
use crate::block_on;
use crate::pipe::ext::PipeExt;
use core::convert::Infallible;
use core::future::Future;
use core::marker::PhantomData;
use proxima_core::markers::{
AllocFree, Commutative, Deterministic, DropSafe, IdempotentSideEffectFree, IsPure, NoStd,
Reproducible, WithoutFilesystem, WithoutNetwork, WithoutRandom, WithoutSpawn, WithoutTime,
};
struct Identity<Value>(PhantomData<fn() -> Value>);
impl<Value> Identity<Value> {
const fn new() -> Self {
Self(PhantomData)
}
}
impl<Value> Pipe for Identity<Value> {
type In = Value;
type Out = Value;
type Err = Infallible;
fn call(&self, input: Value) -> impl Future<Output = Result<Value, Infallible>> {
async move { Ok(input) }
}
}
impl<Value: Send + 'static> SendPipe for Identity<Value> {
type In = Value;
type Out = Value;
type Err = Infallible;
fn call(&self, input: Value) -> impl Future<Output = Result<Value, Infallible>> + Send {
async move { Ok(input) }
}
}
impl<Value> DropSafe for Identity<Value> {}
impl<Value> NoStd for Identity<Value> {}
impl<Value> AllocFree for Identity<Value> {}
impl<Value> IsPure for Identity<Value> {}
impl<Value> Deterministic for Identity<Value> {}
impl<Value> Reproducible for Identity<Value> {}
impl<Value> IdempotentSideEffectFree for Identity<Value> {}
impl<Value> Commutative for Identity<Value> {}
impl<Value> WithoutFilesystem for Identity<Value> {}
impl<Value> WithoutNetwork for Identity<Value> {}
impl<Value> WithoutSpawn for Identity<Value> {}
impl<Value> WithoutTime for Identity<Value> {}
impl<Value> WithoutRandom for Identity<Value> {}
#[derive(Debug, PartialEq, Eq)]
struct Overflow;
struct Increment;
impl Pipe for Increment {
type In = u64;
type Out = u64;
type Err = Overflow;
fn call(&self, input: u64) -> impl Future<Output = Result<u64, Overflow>> {
async move { input.checked_add(1).ok_or(Overflow) }
}
}
#[derive(Debug, PartialEq, Eq)]
enum HalveError {
Odd,
Upstream(Overflow),
}
impl From<Overflow> for HalveError {
fn from(value: Overflow) -> Self {
HalveError::Upstream(value)
}
}
struct Halve;
impl Pipe for Halve {
type In = u64;
type Out = u64;
type Err = HalveError;
fn call(&self, input: u64) -> impl Future<Output = Result<u64, HalveError>> {
async move {
if input.is_multiple_of(2) {
Ok(input / 2)
} else {
Err(HalveError::Odd)
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum DoubleError {
TooBig,
Halve(HalveError),
}
impl From<HalveError> for DoubleError {
fn from(value: HalveError) -> Self {
DoubleError::Halve(value)
}
}
impl From<Overflow> for DoubleError {
fn from(value: Overflow) -> Self {
DoubleError::Halve(HalveError::from(value))
}
}
struct Double;
impl Pipe for Double {
type In = u64;
type Out = u64;
type Err = DoubleError;
fn call(&self, input: u64) -> impl Future<Output = Result<u64, DoubleError>> {
async move { input.checked_mul(2).ok_or(DoubleError::TooBig) }
}
}
struct AlwaysFail;
impl Pipe for AlwaysFail {
type In = u64;
type Out = u64;
type Err = Overflow;
fn call(&self, _input: u64) -> impl Future<Output = Result<u64, Overflow>> {
async move { Err(Overflow) }
}
}
struct SpyRef<'flag> {
ran: &'flag core::cell::Cell<bool>,
}
impl Pipe for SpyRef<'_> {
type In = u64;
type Out = u64;
type Err = HalveError;
fn call(&self, input: u64) -> impl Future<Output = Result<u64, HalveError>> {
self.ran.set(true);
async move { Ok(input) }
}
}
#[test]
fn series_pipes_identity_through_identity() {
let chain = AndThen::new(Identity::<u64>::new(), Identity::<u64>::new());
let out = block_on(Pipe::call(&chain, 41)).expect("infallible identity chain");
assert_eq!(out, 41);
}
#[test]
fn series_composes_distinct_error_types_via_from() {
let chain = Increment.and_then(Halve);
let out = block_on(Pipe::call(&chain, 5)).expect("even intermediate halves");
assert_eq!(out, 3);
}
#[test]
fn series_propagates_first_stage_error_converted_by_from() {
let chain = Increment.and_then(Halve);
let err = block_on(Pipe::call(&chain, u64::MAX)).expect_err("overflow propagates");
assert_eq!(err, HalveError::Upstream(Overflow));
}
#[test]
fn series_surfaces_second_stage_error_unchanged() {
let chain = Increment.and_then(Halve);
let err = block_on(Pipe::call(&chain, 6)).expect_err("odd intermediate rejected");
assert_eq!(err, HalveError::Odd);
}
fn assert_send<Type: Send>(_: &Type) {}
#[test]
fn series_is_a_send_pipe_with_a_send_future() {
let chain = AndThen::new(Identity::<u64>::new(), Identity::<u64>::new());
let future = SendPipe::call(&chain, 7);
assert_send(&future);
let out = block_on(future).expect("send chain");
assert_eq!(out, 7);
}
fn assert_all_markers<Type>()
where
Type: NoStd
+ AllocFree
+ IsPure
+ Deterministic
+ Reproducible
+ IdempotentSideEffectFree
+ Commutative
+ WithoutFilesystem
+ WithoutNetwork
+ WithoutSpawn
+ WithoutTime
+ WithoutRandom
+ DropSafe,
{
}
#[test]
fn series_propagates_all_markers_from_both_stages() {
assert_all_markers::<AndThen<Identity<u64>, Identity<u64>>>();
assert_all_markers::<AndThen<Identity<u64>, AndThen<Identity<u64>, Identity<u64>>>>();
}
#[test]
fn and_then_matches_series_new_for_a_two_stage_chain() {
let fluent = block_on(Pipe::call(&Increment.and_then(Halve), 5));
let manual = block_on(Pipe::call(&AndThen::new(Increment, Halve), 5));
assert_eq!(fluent, manual);
assert_eq!(fluent.expect("even intermediate halves"), 3);
}
#[test]
fn and_then_matches_series_new_on_the_error_path() {
let fluent = block_on(Pipe::call(&Increment.and_then(Halve), u64::MAX));
let manual = block_on(Pipe::call(&AndThen::new(Increment, Halve), u64::MAX));
assert_eq!(fluent, manual);
assert_eq!(
fluent.expect_err("overflow propagates"),
HalveError::Upstream(Overflow)
);
}
#[test]
fn and_then_chain_matches_nested_series_new_for_a_three_stage_chain() {
let left_leaning = Increment.and_then(Halve).and_then(Double);
let right_leaning = AndThen::new(Increment, AndThen::new(Halve, Double));
let left_output = block_on(Pipe::call(&left_leaning, 5)).expect("5 -> 6 -> 3 -> 6");
let right_output = block_on(Pipe::call(&right_leaning, 5)).expect("5 -> 6 -> 3 -> 6");
assert_eq!(left_output, right_output);
assert_eq!(left_output, 6);
}
#[test]
fn and_then_short_circuits_before_the_second_stage_on_first_stage_error() {
let ran = core::cell::Cell::new(false);
let chain = AlwaysFail.and_then(SpyRef { ran: &ran });
let err = block_on(Pipe::call(&chain, 1)).expect_err("first stage always fails");
assert_eq!(err, HalveError::Upstream(Overflow));
assert!(
!ran.get(),
"second stage must not run after first stage errors"
);
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod unpin_tier_tests {
use super::{AndThen, UnpinPipe, UnpinSendPipe};
use core::convert::Infallible;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
struct RingPop(u8);
impl Future for RingPop {
type Output = Result<u8, Infallible>;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.0))
}
}
struct Ring(u8);
impl UnpinPipe for Ring {
type In = ();
type Out = u8;
type Err = Infallible;
fn call(&self, (): ()) -> impl Future<Output = Result<u8, Infallible>> + Unpin {
RingPop(self.0)
}
}
impl UnpinSendPipe for Ring {
type In = ();
type Out = u8;
type Err = Infallible;
fn call(&self, (): ()) -> impl Future<Output = Result<u8, Infallible>> + Send + Unpin {
RingPop(self.0)
}
}
fn merge_in_place<S: UnpinPipe<In = ()>, const N: usize>(
sources: &[S; N],
cx: &mut Context<'_>,
) -> Poll<Result<S::Out, S::Err>> {
for source in sources {
let mut call = source.call(());
if let Poll::Ready(out) = Pin::new(&mut call).poll(cx) {
return Poll::Ready(out);
}
}
Poll::Pending
}
#[test]
fn unpin_future_polls_in_place_without_unsafe_or_alloc() {
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let sources = [Ring(7), Ring(9)];
match merge_in_place(&sources, &mut cx) {
Poll::Ready(Ok(value)) => assert_eq!(value, 7, "first ready source wins"),
other => panic!("expected the first source to be ready, got {other:?}"),
}
}
#[test]
fn send_unpin_tier_is_the_cross_core_mergeable_form() {
fn assert_send_unpin<S: UnpinSendPipe<In = ()>>(source: &S) {
fn needs_send_unpin<F: Future + Send + Unpin>(_: &F) {}
let call = UnpinSendPipe::call(source, ());
needs_send_unpin(&call);
}
assert_send_unpin(&Ring(1));
}
struct Doubler;
impl UnpinPipe for Doubler {
type In = u8;
type Out = u16;
type Err = Infallible;
fn call(&self, input: u8) -> impl Future<Output = Result<u16, Infallible>> + Unpin {
core::future::ready(Ok(u16::from(input) * 2))
}
}
impl UnpinSendPipe for Doubler {
type In = u8;
type Out = u16;
type Err = Infallible;
fn call(&self, input: u8) -> impl Future<Output = Result<u16, Infallible>> + Send + Unpin {
core::future::ready(Ok(u16::from(input) * 2))
}
}
#[test]
fn and_then_unpin_chain_composes_two_pipes_without_a_box() {
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let chain = AndThen::new(Ring(21), Doubler);
let mut call = UnpinPipe::call(&chain, ());
match Pin::new(&mut call).poll(&mut cx) {
Poll::Ready(Ok(value)) => assert_eq!(value, 42, "21 -> ring pop -> double -> 42"),
other => panic!("expected ready, got {other:?}"),
}
}
#[test]
fn and_then_unpin_send_chain_is_send_and_unpin() {
fn needs_send_unpin<F: Future + Send + Unpin>(_: &F) {}
let chain = AndThen::new(Ring(5), Doubler);
let call = UnpinSendPipe::call(&chain, ());
needs_send_unpin(&call);
}
struct PendOnceThenReady {
value: u8,
polled_once: core::cell::Cell<bool>,
}
impl Future for PendOnceThenReady {
type Output = Result<u8, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.polled_once.replace(true) {
Poll::Ready(Ok(self.value))
} else {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
struct SlowRing(u8);
impl UnpinPipe for SlowRing {
type In = ();
type Out = u8;
type Err = Infallible;
fn call(&self, (): ()) -> impl Future<Output = Result<u8, Infallible>> + Unpin {
PendOnceThenReady {
value: self.0,
polled_once: core::cell::Cell::new(false),
}
}
}
#[test]
fn and_then_unpin_chain_resumes_across_polls_without_rerunning_first_stage() {
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let chain = AndThen::new(SlowRing(21), Doubler);
let mut call = UnpinPipe::call(&chain, ());
assert_eq!(
Pin::new(&mut call).poll(&mut cx),
Poll::Pending,
"first stage not ready yet"
);
match Pin::new(&mut call).poll(&mut cx) {
Poll::Ready(Ok(value)) => {
assert_eq!(value, 42, "second poll resumes at First, then runs Second");
}
other => panic!("expected ready on the second poll, got {other:?}"),
}
}
#[derive(Debug, PartialEq, Eq)]
struct RingError;
struct FailingRing;
impl UnpinPipe for FailingRing {
type In = ();
type Out = u8;
type Err = RingError;
fn call(&self, (): ()) -> impl Future<Output = Result<u8, RingError>> + Unpin {
core::future::ready(Err(RingError))
}
}
struct CountingDoubler {
calls: core::cell::Cell<u32>,
}
impl UnpinPipe for CountingDoubler {
type In = u8;
type Out = u16;
type Err = RingError;
fn call(&self, input: u8) -> impl Future<Output = Result<u16, RingError>> + Unpin {
self.calls.set(self.calls.get() + 1);
core::future::ready(Ok(u16::from(input) * 2))
}
}
#[test]
fn and_then_unpin_chain_short_circuits_before_the_second_stage() {
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
let second = CountingDoubler {
calls: core::cell::Cell::new(0),
};
let chain = AndThen::new(FailingRing, second);
let mut call = UnpinPipe::call(&chain, ());
match Pin::new(&mut call).poll(&mut cx) {
Poll::Ready(Err(RingError)) => {}
other => panic!("expected the first stage's error, got {other:?}"),
}
assert_eq!(
chain.second.calls.get(),
0,
"second stage must not run after first stage errors"
);
}
}