Module percolate::projection[][src]

Expand description

Projections asynchronously transform an input A into an output B.

During this process, a reference to self is held.

Naming Scheme

The traits in this module have names of the form 〚Into〛〚Fused〛〚Ref‖Mut〛Projection〚Mut〛.

〚Into〛

This is analogous to the Into in IntoIterator.

Use traits with this fragment in their name with impl or as generic type constraints to accept certain closures directly.

Each type that implements a Projection<A, B> trait should also implement the matching IntoProjection<A, B, IntoProj = Self> trait as identity transformation.

It’s unfortunately (seemingly) not possible to specify this constraint directly on Projection<A, B> without losing meaningful object safety there.

If you have any idea how to add this constraint without a lot of repeating boiler plate, then please let me know! I’ll add it in a future version with a breaking Semver change in that case.

Example

use ergo_pin::ergo_pin;
use percolate::projection::{IntoProjection, Projection};

#[ergo_pin]
async fn project<A, B, X>(value: A, projection: impl IntoProjection<A, B, X>) -> B {
    pin!(
        projection.into_projection() // impl Projection<A, B>
    )                                // Pin<&mut impl Projection<A, B>>
        .into_ref()                  // Pin<&impl Projection<A, B>>
        .project(value)              // PinHandleMut<dyn Future<B>>
        .await                       // B
}

.into_…() Proxy

As the IntoProjection traits in this module are object-safe, it makes sense to use a proxy for the initial conversion:

use core::pin::Pin;
use ergo_pin::ergo_pin;
use percolate::projection::{IntoProjection, Projection};

#[ergo_pin]
async fn project_heavy<A, B, X>(value: A, projection: impl IntoProjection<A, B, X>) -> B {
    return project_heavy_dyn(
        value,
        pin!(projection.into_projection()),
    ).await;

    async fn project_heavy_dyn<A, B>(value: A, projection: Pin<&mut dyn Projection<A, B>>) -> B {
        // Do significant work in this function.
        projection.project(value).await
    }
}

The inner function is then monomorphic over the type of projection, which can significantly reduce the generated executable size.

TODO: Provide an attribute macro that can heuristically perform this transformation, and also generate disambiguation args automatically.

〚Fused〛

These projections generate FusedFutures, which keep track of whether they are allowed to be .poll(…)ed again through their .is_terminated() method.

Note that some adapters, like AsyncMut, are dependently fused. If the underlying projection generates a FusedFuture, then so do they when called through their respective Fused… trait’s …fused(…) method.

Practically speaking, it’s the same underlying type, but this is not guaranteed!

  • casting: Fused -> Fused

〚Ref‖Mut〛

Types with Ref or Mut in their name in this position project from an “any lifetime” reference (&'_ A) or mutable reference (&'_ mut A) to their output type. This reference stays borrowed for at least as long as the resulting PinHandleMut/Future exists.

Without generic associated types or traits that can be implemented over the “any” lifetime, it’s unfortunately currently not possible to fully unify types over whether they accept their parameter by value or by (mutable) reference with “any” lifetime (in a way that’s nice to work with. Some workarounds using fn as generic type parameter should work but would be less easy to use).

The object-safe Into Ref/Mut traits like RefProjection<A, B> can already be expressed as trait aliases, in this case for example over for<'a> Projection<&'a A, B>, and are blanket-implemented as such.

When implementing a custom projection, implement the underlying Ref/Mut trait for any lifetime. The aliased shorthand then becomes available automatically.

  • casting: Mut -> Ref -> Mut, Ref -> Ref

Trailing 〚Mut〛

The projection itself is mutably borrowed.

Note that most simple projections still require this to store their parameter, as object-safety within a no-std crate doesn’t leave room for temporary allocations.

  • casting: Mut -> Mut

Example

use ergo_pin::ergo_pin;
use percolate::projection::{AsyncMut, IntoProjectionMut, ProjectionMut};
use pollster::block_on;
use tap::Conv;

#[ergo_pin]
async fn project<A, B, X>(value: A, projection: impl IntoProjectionMut<A, B, X>) -> B {
    pin!(
        projection.into_projection_mut() // impl ProjectionMut<A, B>
    )                                    // Pin<&mut impl ProjectionMut<A, B>>
        .project(value)                  // PinHandleMut<dyn Future<B>>
        .await                           // B
}

assert_eq!(block_on(project(1, |x: u8| x + 1)), 2);
assert_eq!(
    block_on(project(
        1,
        // Type inference doesn't understand this on its own (yet), unfortunately.
        // We can instead pass the projection pre-converted.
        (|x| async move { x + 1 }).conv::<AsyncMut<_, _, _, _>>()),
    ),
    2,
);

Structs

AsyncMut

From<P: FnMut(A) -> F: 〚Fused〛Future<Output = B>>> and 〚Fused〛ProjectionMut<A, B>

FusedBlockingMut

From<P: FnMut(A) -> B>> and FusedProjectionMut<A, B>

FusedMutBlockingMut

From<P: FnMut(&mut A) -> B>> and FusedMutProjectionMut<A, B>

FusedRefBlockingMut

From<P: FnMut(&A) -> B>> and FusedRefProjectionMut<A, B>

Traits

FusedMutProjection

alias: for<'a> FusedProjection<&'a mut A, B>

FusedMutProjectionMut

alias: for<'a> FusedProjectionMut<&'a mut A, B>

FusedProjection
FusedProjectionMut
FusedRefProjection

alias: for<'a> FusedProjection<&'a A, B>

FusedRefProjectionMut

alias: for<'a> FusedProjectionMut<&'a A, B>

IntoFusedMutProjection
IntoFusedMutProjectionMut
IntoFusedProjection
IntoFusedProjectionMut
IntoFusedRefProjection
IntoFusedRefProjectionMut
IntoMutProjection
IntoMutProjectionMut
IntoProjection
IntoProjectionMut
IntoRefProjection
IntoRefProjectionMut
MutProjection

alias: for<'a> Projection<&'a mut A, B>

MutProjectionMut

alias: for<'a> ProjectionMut<&'a mut A, B>

Projection
ProjectionMut
RefProjection

alias: for<'a> Projection<&'a A, B>

RefProjectionMut

alias: for<'a> ProjectionMut<&'a A, B>

Functions

from_async_mut

FnMut(A) -> 〚Fused〛Future<Output = B>〚Fused〛ProjectionMut<A, B>

from_blocking_mut

FnMut(A) -> BFusedProjectionMut<A, B>

from_mut_blocking_mut

FnMut(&mut A) -> BFusedMutProjectionMut<A, B>

from_ref_blocking_mut

FnMut(&A) -> BFusedRefProjectionMut<A, B>