combinator
import std::future::combinator; · source
Join, TryJoin, Select, and Timeout are ordinary futures that own the futures they compose and drive them from their own poll. Every child is polled with the same Context, so they all register the composing task's waker: whichever child becomes ready wakes the task, the task re-polls the combinator, and the combinator re-polls whichever children have not finished.
One task drives the whole group. No child is spawned, and none needs an executor.
Types
Futures
type struct Futures {
static join<A, B, OA, OB>(a: A, b: B) -> Join<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>;
static try_join<A, B, T1, T2, E>(a: A, b: B) -> TryJoin<A, B, T1, T2, E>
where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>;
static select<A, B, OA, OB>(a: A, b: B) -> Select<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>;
static timeout<F, O>(dur: Duration, fut: F) -> Timeout<F, O>
where F: Future<O>;
static timeout_at<F, O>(deadline: Instant, fut: F) -> Timeout<F, O>
where F: Future<O>;
}
Build them through Futures, which is a namespace rather than a value — a combinator is generic over its children's types as well as their outputs, and a child produced by an async function has a compiler-generated type no caller can name. Constructing through statics whose bounds infer the whole set is what makes them constructible at all.
join completes once both children have finished; try_join once both succeed, or with the first Err either reports — dropping, and so cancelling, the sibling. select completes as soon as either child does, and timeout / timeout_at race a child against a duration or an absolute Instant.
Static functions
static join<A, B, OA, OB>(a: A, b: B) -> Join<A, B, OA, OB> where A: Future<OA>, B: Future<OB>;
static join<A, B, OA, OB>(a: A, b: B) -> Join<A, B, OA, OB> where A: Future<OA>, B: Future<OB>;Run a and b concurrently; complete with both values.
static try_join<A, B, T1, T2, E>(a: A, b: B) -> TryJoin<A, B, T1, T2, E> where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>;
static try_join<A, B, T1, T2, E>(a: A, b: B) -> TryJoin<A, B, T1, T2, E> where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>;Run a and b concurrently; complete with both values, or with the first error either reports, dropping the sibling.
static select<A, B, OA, OB>(a: A, b: B) -> Select<A, B, OA, OB> where A: Future<OA>, B: Future<OB>;
static select<A, B, OA, OB>(a: A, b: B) -> Select<A, B, OA, OB> where A: Future<OA>, B: Future<OB>;Run a and b concurrently; complete as soon as either does, dropping the other.
static timeout<F, O>(dur: Duration, fut: F) -> Timeout<F, O> where F: Future<O>;
static timeout<F, O>(dur: Duration, fut: F) -> Timeout<F, O> where F: Future<O>;Run fut with a deadline dur from now.
static timeout_at<F, O>(deadline: Instant, fut: F) -> Timeout<F, O> where F: Future<O>;
static timeout_at<F, O>(deadline: Instant, fut: F) -> Timeout<F, O> where F: Future<O>;Run fut with an absolute deadline.
Join and TryJoin
type struct Join<A, B, OA, OB> {
a: A;
b: B;
a_out: Option<OA>;
b_out: Option<OB>;
a_done: boolean;
b_done: boolean;
}
type struct TryJoin<A, B, T1, T2, E> {
a: A;
b: B;
a_out: Option<T1>;
b_out: Option<T2>;
a_done: boolean;
b_done: boolean;
done: boolean;
}
Join completes when both children have; TryJoin completes early with the first Err either child reports, dropping the sibling — which cancels it.
Join
Properties
| Property | Type |
|---|---|
a | A |
b | B |
a_out | Option<OA> |
b_out | Option<OB> |
a_done | boolean |
b_done | boolean |
TryJoin
Properties
| Property | Type | Notes |
|---|---|---|
a | A | |
b | B | |
a_out | Option<T1> | |
b_out | Option<T2> | |
a_done | boolean | |
b_done | boolean | |
done | boolean | Set by BOTH completion paths. a_done && b_done cannot stand in for it: an error completes this future with the other side still unfinished, so without a separate flag a later poll would drive a child again after this future had already reported. |
Select
type struct Select<A, B, OA, OB> {
a: A;
b: B;
done: boolean;
}
type enum Selected<L, R> {
First(L);
Second(R);
}
select polls a first on every poll. That bias is deliberate and documented rather than randomized: a select returns on the first ready child, so a consistently-ready a cannot starve b within one select — it only makes the choice predictable when both are ready at once, which is exactly what a caller pairing an operation against a timeout wants.
Select
Properties
| Property | Type |
|---|---|
a | A |
b | B |
done | boolean |
Selected
Variants
| Variant |
|---|
First(L) |
Second(R) |
Timeout
type struct Timeout<F, O> {
fut: F;
sleep: Sleep;
done: boolean;
}
type struct Elapsed {
deadline: i64;
static at(deadline: i64) -> Elapsed;
deadline_nanos(&this) -> i64;
}
timeout polls the operation before the deadline, so a future that becomes ready on the very poll where the timer also fires reports its value rather than a timeout. It did complete; calling that a timeout would discard a result the caller can never get back — and for an I/O future, one whose side effect has already happened.
Elapsed carries the deadline it missed, as monotonic nanoseconds on the same scale Instant::as_nanos reports.
Timeout
Properties
| Property | Type |
|---|---|
fut | F |
sleep | Sleep |
done | boolean |
Elapsed
Properties
| Property | Type |
|---|---|
deadline | i64 |
Constructors
static at(deadline: i64) -> Elapsed;
Methods
deadline_nanos(&this) -> i64;
deadline_nanos(&this) -> i64;The monotonic-nanosecond deadline that was missed.
Cancellation is a drop
A Select that completes still owns the child that lost, and a Timeout that elapses still owns the operation it was timing. Both are released when the combinator itself is dropped, and that is what cancels them: dropping a parked future runs its Drop, which is where a Sleep disarms its timer and an I/O future releases its reactor registration. TryJoin short-circuiting on an error is the same mechanism — the sibling is dropped, and the drop cancels it.
Cancellation therefore needs no separate mechanism and cannot be forgotten — it is the same drop that reclaims the memory.
Arity
These compose two futures. Higher arities nest: Futures::join(a, Futures::join(b, c)). Cryo has no variadic generics, so a fixed-arity pair plus nesting is the whole story; the alternative would be a hand-written Join3, Join4, ... tower that says nothing new.
Polling any of these after it completes panics, matching
Ready. A completed future has already moved its output out, so a second poll has nothing to return — and would re-poll children that already finished, itself a contract violation.
Trait implementations
Join
implement<A, B, OA, OB> trait Future<(OA, OB)> for struct Join<A, B, OA, OB> where A: Future<OA>, B: Future<OB>
TryJoin
implement<A, B, T1, T2, E> trait Future<Result<(T1, T2), E>> for struct TryJoin<A, B, T1, T2, E> where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>
Select
implement<A, B, OA, OB> trait Future<Selected<OA, OB>> for struct Select<A, B, OA, OB> where A: Future<OA>, B: Future<OB>
Timeout
implement<F, O> trait Future<Result<O, Elapsed>> for struct Timeout<F, O> where F: Future<O>
type struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type struct Name {
field: Type;
method(&this) -> Ret { ... }
}A value type with named fields and optional methods. Structs live on the stack and are passed by value. Fields are public by default and may carry = default values; methods take their receiver as &this, mut &this, or this.
type struct Futures {}Constructors for the combinators above.
static name(param: Type) -> Ret { ... }A method that belongs to the type rather than an instance, called as Type::name(..). For structs, static new(..) returning a struct literal is the idiomatic constructor.
static Futures::join<A, B, OA, OB>(a: A, b: B) -> Join<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>Run a and b concurrently; complete with both values.
type parameter Awhere A: Future
type parameter Bwhere B: Future
type parameter OAtype parameter OB(parameter) a: A(parameter) b: Btype struct Join<A, B, OA, OB> {
a: A;
b: B;
a_out: Option<OA>;
b_out: Option<OB>;
a_done: boolean;
b_done: boolean;
}Runs two futures concurrently and completes with both results once both have finished. Each child is polled only until it completes; its value is parked in an Option until the other one catches up.
type parameter OAtype parameter OBwhere T: Ord + Clone, U: HashConstrains generic parameters to types that implement the listed traits, which is what makes those traits' methods callable on T. Bounds on one parameter join with +; parameters separate with ,. A where on a single method narrows the type's parameter for that method only.
type trait Future {
type Output;
poll(mut &this, cx: Context*) -> Poll<This::Output>;
}An asynchronous computation. poll drives it one step: Ready(v) when the value is available, Pending when it is not yet (the future has arranged, via cx's waker, to be polled again later).
static Futures::try_join<A, B, T1, T2, E>(a: A, b: B) -> TryJoin<A, B, T1, T2, E>
where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>Run a and b concurrently; complete with both values, or with the first error either reports, dropping the sibling.
type parameter T1type parameter T2type parameter Etype struct TryJoin<A, B, T1, T2, E> {
a: A;
b: B;
a_out: Option<T1>;
b_out: Option<T2>;
a_done: boolean;
b_done: boolean;
done: boolean;
}Runs two fallible futures concurrently and completes with both values, or with the FIRST error either of them reports.
std::future::combinatortype parameter T1type parameter T2type parameter Estatic Futures::select<A, B, OA, OB>(a: A, b: B) -> Select<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>Run a and b concurrently; complete as soon as either does, dropping the other.
type struct Select<A, B, OA, OB> {
a: A;
b: B;
done: boolean;
}Runs two futures concurrently and completes as soon as EITHER finishes, reporting which one it was. The loser is dropped with this future, which cancels it.
std::future::combinatortype parameter OAtype parameter OBstatic Futures::timeout<F, O>(dur: Duration, fut: F) -> Timeout<F, O>
where F: Future<O>Run fut with a deadline dur from now.
type parameter Fwhere F: Future
type parameter O(parameter) dur: Durationtype struct Duration {
secs: u64;
nanos: u32;
}A length of time: whole secs plus a sub-second nanos remainder (0 <= nanos < 1_000_000_000). POD; copy-by-value, no drop.
(parameter) fut: Ftype struct Timeout<F, O> {
fut: F;
sleep: Sleep;
done: boolean;
}Runs a future with a deadline: Ok(value) if it finishes in time, otherwise Err(Elapsed).
type parameter Ostatic Futures::timeout_at<F, O>(deadline: Instant, fut: F) -> Timeout<F, O>
where F: Future<O>Run fut with an absolute deadline.
(parameter) deadline: Instanttype struct Instant {
secs: i64;
nanos: i64;
}A measurement of the monotonic clock. Opaque: the fields are the raw timespec, meaningful only relative to another Instant. POD.
type parameter Otype struct Join<A, B, OA, OB> {
a: A;
b: B;
a_out: Option<OA>;
b_out: Option<OB>;
a_done: boolean;
b_done: boolean;
}Runs two futures concurrently and completes with both results once both have finished. Each child is polled only until it completes; its value is parked in an Option until the other one catches up.
type parameter Atype parameter BJoin.a: Atype parameter AJoin.b: Btype parameter BJoin.a_out: Option<OA>type parameter OAJoin.b_out: Option<OB>type parameter OBJoin.a_done: booleanJoin.b_done: booleantype struct TryJoin<A, B, T1, T2, E> {
a: A;
b: B;
a_out: Option<T1>;
b_out: Option<T2>;
a_done: boolean;
b_done: boolean;
done: boolean;
}Runs two fallible futures concurrently and completes with both values, or with the FIRST error either of them reports.
TryJoin.a: Atype parameter ATryJoin.b: Btype parameter BTryJoin.a_out: Option<T1>type parameter T1TryJoin.b_out: Option<T2>type parameter T2TryJoin.a_done: booleanTryJoin.b_done: booleanTryJoin.done: booleanSet by BOTH completion paths. a_done && b_done cannot stand in for it: an error completes this future with the other side still unfinished, so without a separate flag a later poll would drive a child again after this future had already reported.
type struct Select<A, B, OA, OB> {
a: A;
b: B;
done: boolean;
}Runs two futures concurrently and completes as soon as EITHER finishes, reporting which one it was. The loser is dropped with this future, which cancels it.
Select.a: Atype parameter ASelect.b: Btype parameter BSelect.done: booleantype enum Name {
Unit,
Payload(T),
Named { x: i32 },
}An algebraic data type. Variants may be bare or carry a tuple or struct payload, and a match on an enum must cover every variant. Name::Variant names a variant; methods are added with implement enum Name { ... }.
type enum Selected<L, R> {
First(L);
Second(R);
}Which side of a Select finished first.
type parameter Ltype parameter RSelected::First(L)type parameter LSelected::Second(R)type parameter Rtype struct Timeout<F, O> {
fut: F;
sleep: Sleep;
done: boolean;
}Runs a future with a deadline: Ok(value) if it finishes in time, otherwise Err(Elapsed).
type parameter FTimeout.fut: Ftype parameter FTimeout.sleep: Sleeptype struct Sleep {
deadline: i64;
id: u64;
rt: Reactor*;
}A future that completes once the monotonic clock reaches its deadline.
std::future::timerTimeout.done: booleantype struct Elapsed {
deadline: i64;
}A Timeout expired before the operation it was timing finished.
Elapsed.deadline: i64static Elapsed::at(deadline: i64) -> Elapsed(parameter) deadline: i64type struct Elapsed {
deadline: i64;
}A Timeout expired before the operation it was timing finished.
Elapsed::deadline_nanos(&this) -> i64The monotonic-nanosecond deadline that was missed.
&this
mut &this
thisThe receiver of a method. Its first parameter declares how the method takes it: &this borrows read-only, mut &this borrows exclusively, and this / mut this move the value in and consume it. In the body, this.field reaches the members.
import a::b;
import a::b::{ X, Y };
import a::b as c;
import a::b::*;Brings a module, or selected items from it, into scope. One path per declaration: use the brace form for several items, as to alias, and * for everything public (prefer the brace form; wildcards invite name collisions).
namespace std::future;std::future: stackless async foundations — Future, Poll, Context, Waker, and the drivers that poll them.
namespace std::future::combinator;std::future::combinator: running futures against each other — Join, TryJoin, Select, and Timeout.
type struct Context {
waker: Waker;
}The context threaded through a poll call. Holds the Waker a future registers when it returns Pending. Passed by pointer down the poll tree, matching the stdlib convention of moving aggregates by pointer.
async function name(param: Type) -> T { ... }Compiles the function into a stackless state machine. Calling it builds a future whose output is the declared return type; nothing runs until that future is polled. Allowed on free functions, methods, and trait methods.
language referencefunction name<T>(param: Type) -> Ret { ... }Declares a function. Parameters are written name: Type and are never inferred; the return type follows -> and defaults to void. Declaration order does not matter: every signature is collected before any body is checked, so functions may call each other freely and recurse.
static Futures::join<A, B, OA, OB>(a: A, b: B) -> Join<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>Run a and b concurrently; complete with both values.
static Futures::try_join<A, B, T1, T2, E>(a: A, b: B) -> TryJoin<A, B, T1, T2, E>
where A: Future<Result<T1, E>>, B: Future<Result<T2, E>>Run a and b concurrently; complete with both values, or with the first error either reports, dropping the sibling.
Result::Err(E)static Futures::select<A, B, OA, OB>(a: A, b: B) -> Select<A, B, OA, OB>
where A: Future<OA>, B: Future<OB>Run a and b concurrently; complete as soon as either does, dropping the other.
static Futures::timeout<F, O>(dur: Duration, fut: F) -> Timeout<F, O>
where F: Future<O>Run fut with a deadline dur from now.
static Futures::timeout_at<F, O>(deadline: Instant, fut: F) -> Timeout<F, O>
where F: Future<O>Run fut with an absolute deadline.
Join.a: AJoin.b: Bconst E: f64 = 2.718281828459045;Timeout.fut: FJoin.a_out: Option<OA>Join.b_out: Option<OB>Join.a_done: booleanJoin.b_done: booleanTryJoin.done: booleanSet by BOTH completion paths. a_done && b_done cannot stand in for it: an error completes this future with the other side still unfinished, so without a separate flag a later poll would drive a child again after this future had already reported.
Selected::First(L)Selected::Second(R)Instant::as_nanos(&this) -> i64This reading as raw monotonic nanoseconds. The absolute value is meaningful only relative to another Instant - it counts from an unspecified epoch (typically system boot) - so use it for lightweight elapsed-time math where the difference of two readings is all that matters and carrying a Duration would be overkill.
Timeout.sleep: SleepElapsed.deadline: i64static Elapsed::at(deadline: i64) -> ElapsedElapsed::deadline_nanos(&this) -> i64The monotonic-nanosecond deadline that was missed.
std::future::combinatortype struct Ready<T> {
value: Option<T>;
}A future that is already complete: the first poll returns Ready(value). The value is held in an Option and moved out on completion, so polling a Ready a second time is a contract violation (it panics) rather than a double-move.
implement struct Name { ... }
implement trait Trait for Type { ... }
implement u64 { ... }Adds methods to a type outside its declaration. implement struct T (or enum, or a bare primitive) opens an inherent block; implement trait Tr for T supplies a trait's methods for one type. Resolution is at compile time - no dynamic dispatch.
type trait Name : Base {
method(&this, other: &This) -> Ret;
}Names a set of methods a type may implement. Inside the body This is the implementing type; methods may carry default bodies, and a trait may require a base trait (type trait Ord : Eq). Generic code asks for a trait with where T: Name.
implement trait Trait for Type { ... }Names the type a trait implementation is for. The block that follows supplies the bodies the trait requires.
language referencetype enum Selected<L, R> {
First(L);
Second(R);
}Which side of a Select finished first.