Expand description
Library for broadcasting messages/events such as change notifications.
§What nosy does
The niche which nosy seeks to fill is: delivering precise change notifications
(e.g. “these particular elements of this collection have changed”) from a data source
to a set of listeners (observers) in such a way that
- there is no unbounded buffering of messages (as an unbounded channel would have),
- there is no blocking/suspending (as a bounded channel would have),
- there is no execution of further application logic while the message is being delivered (as plain event-listener registration would have), and
- the scheduling of the execution of said application logic is fully under application control (rather than implicitly executing some sort of work queue, as a “reactive” framework might).
The tradeoff we make in order to achieve this is that message delivery does involve execution
of a small amount of code on behalf of each Listener;
this code is responsible for deciding whether the message is of interest, and if so, storing it
or its implications for later reading.
(We could say that the listeners are nosy.)
For example, a listener might match a message enum, and in cases where it is of interest,
set an AtomicBool to true. The final recipient would then use that boolean flag
to determine that it needs to re-read the data which the messages are about.
Thus, the message itself need not be stored until the final recipient gets to it,
and multiple messages are de-duplicated.
In a loose sense, each listener is itself a sort of channel sender;
it is written when the message is sent, and to be useful, it must have shared state with
some receiving side which reads (and clears) that shared state.
Because of this, nosy is not a good choice if you expect to have very many listeners
of the same character (e.g. many identical worker tasks updating their state); in those cases,
you would probably be better off using a conventional broadcast channel or watch channel.
It is also not a good choice if it is critical that no third-party code executes on your thread
or while your function is running.
§Getting started
The types in this library are often generic over whether they are Send + Sync
and require the values and listeners they contain to be too. For convenience, a set of
less-generic type aliases and functions is available in the sync and unsync modules.
The following discussion links to the generic versions, but examples will use the non-generic
ones.
-
To send messages, create a
Notifier, which manages a collection ofListeners. -
To receive messages, create a
Listener, then use theListentrait to register it with aNotifieror something which contains aNotifier. When possible, you should use existingListenerimplementations such asFlagorStoreLockwhich have been designed to be well-behaved, but it is also reasonable to write your own implementation, as long as it obeys the documented requirements. -
To share a value which changes over time and which can be retrieved at any time (rather than only a stream of messages), use
Cell, or implementSource.
§Features and platform requirements
nosy is compatible with no_std platforms.
The minimum requirements for using nosy are the following.
(All platforms which support std meet these requirements, and many others do too.)
- The
allocstandard library crate, and a global allocator. - Pointer-sized and
u8-sized atomics (cfg(target_has_atomic = "ptr")andcfg(target_has_atomic = "8")).
The following Cargo feature flags are defined:
-
"async": Add functionality forasyncprogramming, currently consisting of thefuturemodule. -
"std": Enable implementations of our traits forstdtypes, rather than onlycoreandalloctypes. -
"std-sync": Makes use ofstd::syncto addSynctoNotifier,StoreLock, andFlatten.Adds the type aliases
nosy::sync::{Cell, CellWithLocal, CellSource}, which depend onstd::sync::Mutex. -
"spin-sync": Makes use of spinlocks to addSynctoNotifier,StoreLock, andFlatten. This is not recommended for general use and is primarily useful whenSyncis required yet the platform isno_stdand single-threaded. If both"spin-sync"and"std-sync"are enabled,"std-sync"takes priority.
It is possible to use a subset of nosy’s functionality as Send + Sync
without enabling this feature and without depending on std.
In particular, you may wish to use sync::RawNotifier; the Flag and
future::WakeFlag
listeners are always Send + Sync; and Cell can have its internal interior mutability
swapped out if you implement the LoadStore trait.
§Limitations
Besides the logical consequences of the architecture, there are some costs due to implementation restrictions that could have been avoided in a more narrowly scoped library, and some missing features.
-
In some cases,
Arcis used whereRcwould do. This is done in order to avoid unnecessary incompatibilities between thesyncandunsyncstyles of usage (e.g.Cell::as_source()’s return type does not vary). -
Currently, almost all
Listenerinvocations end up passing through double indirection. This is because by default, eachListenerin aNotifieris stored as anArc<dyn Listener>, and most listeners then contain a weak reference to their actual target. This could be fixed with “inline box” storage of listeners whose data size is small, but that is not provided as default functionality.In specific applications, this can be avoided by creating
Notifiers which store a single non-dynlistener type or enum of listener types. SeeFromListenerfor more information. -
There is not yet any support for bringing your own
Mutextype to enableNotifierandStoreLockto beSend + Syncwithoutstd. If you need these features, you must useRawNotifierand implement the other components yourself.
Modules§
- future
- Integration with
asyncprogramming. - sync
- Type aliases for use in applications where listeners are expected to implement
Sync. - unsync
- Type aliases for use in applications where listeners are not expected to implement
Sync.
Structs§
- Buffer
- Buffers messages that are to be sent through a
Notifier, for efficiency. - Cell
- An interior-mutable container for a single value, which notifies when the value is changed.
- Cell
Source Cell::as_source()implementation.- Cell
With Local - Like
Cell, but allows borrowing the current value, at the cost of requiring&mutaccess to set it, and storing an extra clone. - Constant
- A
Sourceof a constant value; never sends a change notification. - Filter
- A
Listenerwhich transforms or discards messages before passing them on to anotherListener. - Flag
- A
Listenerdestination which records only whether any messages have been received, until cleared. - Flag
Listener Flag::listener()implementation.- Flatten
- A
Sourcethat takes aSourcewhose value is anotherSourceand produces the value of the latter source. - Gate
- Breaks a
Listenerconnection when dropped. - Gate
Listener - A
Listenerwhich forwards messages to another, until the correspondingGateis dropped. - Log
- A
Listenerdestination which stores all the messages it receives. - LogListener
Log::listener()implementation.- Map
- A
Sourcewhose values are produced by applying a function to another source. - Notifier
- Delivers messages to a dynamic set of
Listeners. - Notifier
Forwarder - A
Listenerwhich forwards messages through aNotifierto its listeners. Constructed byNotifier::forwarder(). - Null
Listener - A
Listenerwhich discards all messages. - RawBuffer
- Buffers messages that are to be sent through a
RawNotifier, for efficiency. - RawNotifier
Notifierbut without interior mutability.- Store
Lock - Records messages delivered via
Listenerby mutating a value which implementsStore. - Store
Lock Listener StoreLock::listener()implementation.
Traits§
- From
Listener - Conversion from a specific listener type to a more general listener type, such as a trait object.
- Into
Listener - Conversion from a concrete listener type to (normally) some flavor of boxed trait object.
- Listen
- Ability to subscribe to a source of messages, causing a
Listenerto receive them as long as it wishes to. - Listener
- A receiver of messages (typically from something implementing
Listen) which can indicate when it is no longer interested in them (typically because the associated recipient has been dropped). - Load
Store - Interior-mutable storage for a value that can be replaced or copied.
- Source
- Access to a value that might change and notifications when it does.
- Store
- A data structure which records received messages for later processing.
- Store
Ref - An interior-mutable data structure which records received messages for later processing.