Crate nosy

Crate nosy 

Source
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 of Listeners.

  • To receive messages, create a Listener, then use the Listen trait to register it with a Notifier or something which contains a Notifier. When possible, you should use existing Listener implementations such as Flag or StoreLock which 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 implement Source.

§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 alloc standard library crate, and a global allocator.
  • Pointer-sized and u8-sized atomics (cfg(target_has_atomic = "ptr") and cfg(target_has_atomic = "8")).

The following Cargo feature flags are defined:

  • "async": Add functionality for async programming, currently consisting of the future module.

  • "std": Enable implementations of our traits for std types, rather than only core and alloc types.

  • "std-sync": Makes use of std::sync to add Sync to Notifier, StoreLock, and Flatten.

    Adds the type aliases nosy::sync::{Cell, CellWithLocal, CellSource}, which depend on std::sync::Mutex.

  • "spin-sync": Makes use of spinlocks to add Sync to Notifier, StoreLock, and Flatten. This is not recommended for general use and is primarily useful when Sync is required yet the platform is no_std and 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, Arc is used where Rc would do. This is done in order to avoid unnecessary incompatibilities between the sync and unsync styles of usage (e.g. Cell::as_source()’s return type does not vary).

  • Currently, almost all Listener invocations end up passing through double indirection. This is because by default, each Listener in a Notifier is stored as an Arc<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-dyn listener type or enum of listener types. See FromListener for more information.

  • There is not yet any support for bringing your own Mutex type to enable Notifier and StoreLock to be Send + Sync without std. If you need these features, you must use RawNotifier and implement the other components yourself.

Modules§

future
Integration with async programming.
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.
CellSource
Cell::as_source() implementation.
CellWithLocal
Like Cell, but allows borrowing the current value, at the cost of requiring &mut access to set it, and storing an extra clone.
Constant
A Source of a constant value; never sends a change notification.
Filter
A Listener which transforms or discards messages before passing them on to another Listener.
Flag
A Listener destination which records only whether any messages have been received, until cleared.
FlagListener
Flag::listener() implementation.
Flatten
A Source that takes a Source whose value is another Source and produces the value of the latter source.
Gate
Breaks a Listener connection when dropped.
GateListener
A Listener which forwards messages to another, until the corresponding Gate is dropped.
Log
A Listener destination which stores all the messages it receives.
LogListener
Log::listener() implementation.
Map
A Source whose values are produced by applying a function to another source.
Notifier
Delivers messages to a dynamic set of Listeners.
NotifierForwarder
A Listener which forwards messages through a Notifier to its listeners. Constructed by Notifier::forwarder().
NullListener
A Listener which discards all messages.
RawBuffer
Buffers messages that are to be sent through a RawNotifier, for efficiency.
RawNotifier
Notifier but without interior mutability.
StoreLock
Records messages delivered via Listener by mutating a value which implements Store.
StoreLockListener
StoreLock::listener() implementation.

Traits§

FromListener
Conversion from a specific listener type to a more general listener type, such as a trait object.
IntoListener
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 Listener to 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).
LoadStore
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.
StoreRef
An interior-mutable data structure which records received messages for later processing.