Crates.io | senders_receivers |
lib.rs | senders_receivers |
version | 0.0.7 |
source | src |
created_at | 2024-07-18 15:14:09.054869 |
updated_at | 2024-08-10 15:03:45.392136 |
description | Senders/Receivers implementation for Rust. |
homepage | |
repository | https://github.com/nahratzah/senders_receivers |
max_upload_size | |
id | 1307567 |
size | 417,417 |
This is an implementation (for certain value of implementation) of C++ senders/receivers.
Please refer to the rust documentation for how to use this.
This code is very much a work-in-progress, and large parts are still missing. The API is also still in flux.
use senders_receivers::*;
let sender = Just::from((1, 2, 3, 4))
| Then::from(|(a, b, c, d)| (a * b * c * d,));
println!("outcome: {}", sender.sync_wait().expect("no error").expect("no cancelation").0);
What this does:
Just::from
: declares an starting value for the sender chain.Then::from
: declares a transformation on these values.|
(the pipe symbol): used to bind them together.None of the steps are run, until sync_wait
is invoked.
The main concept of this code comes from having both senders and receivers. A sender is a type that produces a value signal (or an error signal, or a done signal). A receiver is a type that accepts this signal.
Senders are composable: they can be chained together, creating a new sender.
This is done with the |
(pipe) symbol.
If you are using the library, you will not spot any receivers, unless you're implementing your own extensions.
Each sender, produces either
a value signal
,
an error signal
,
or a done signal
.
Exactly one of these will be produced.
A value signal
indicates that the sender completed successfully, and produced a value.
An error signal
indicates that the sender failed, and produced an [Error].
A done signal
indicates that the sender canceled its work (producing neither a value, nor an error).
Each operation will run on a [Scheduler]. (Sometimes more than one, for example if you [Transfer] to a different scheduler.)
A scheduler encapsulates the concept of CPU-time. Different schedulers will have different characteristics, related to when/where things run.
Currently, the following schedulers are implemented:
An attempt is made to remain close the the usability of its C++ counterpart, but sacrifices had to be made.
Error
type is type-erased/dynamic.Value
type is a tuple.Value
is not a variant.connect
call can fail, and this will result in an error being propagated.
This meant that when the connect
call inside a let_value
step fails,
the error would have to propage via the receiver.
The same receiver would also be passed to the connect call, using move semantics.connect
fails, it'll have to create an operation state that'll propagate an error.Some sacrifices stem from me disagreeing with the C++ design: I liked the promise from the design, that scheduler changes are explicit only. But in practice, it was very hard to use, and my code, once async, always needed to grab the receiver-scheduler (because it was too common for the sender not to have a (known) scheduler).
done
and error
channels no longer have an associated scheduler.value
channel now always has an associated scheduler.aio_write
) to suspend execution,
and resume on the same scheduler code was running on earlier.The reason that error
channels no longer have an associated scheduler,
is because scheduler-transfers can fail, and this would break the invariant of an error-scheduler.
Dropping the scheduler from done
and error
signals, means that scheduler switches will only happen on the happy path
(and on recovery paths).