Monad and functional-programming utilities for Rust — Rx-style streams, coroutines, actors, and small FP helpers.
Functional and reactive patterns are awkward to express in Rust, and few crates cover this niche. fpRust fills part of that gap. See ROADMAP.md for maintenance posture and priorities.
Rust: edition 2021, MSRV 1.56 (rust-version in Cargo.toml). The default pure build pulls no async dependencies.
Cargo features are layered. Defaults enable the synchronous pure stack only.
| Feature | What it enables |
|---|---|
pure (default) |
fp, maybe, sync, cor, actor, handler, monadio, publisher modules |
for_futures |
Adds optional futures / futures-test dependencies only — does not expose APIs by itself |
test_runtime |
pure + for_futures — use for cargo test, examples, and any code that needs Future integrations |
Module-scoped flags (fp, maybe, sync, cor, actor, handler, monadio, publisher) are included via pure. APIs such as MonadIO::to_future() or Publisher::subscribe_as_stream() are gated on for_futures plus the owning module (for example monadio or publisher).
WillAsync is built when publisher and handler are enabled. Its Future implementation requires for_futures + publisher + handler — equivalently, enable test_runtime (or pure with for_futures).
CountDownLatch’s Future impl needs only for_futures (and sync, which pure already pulls in).
- MonadIO (
fp_rust::monadio) — map / fmap / subscribe, sync and async scheduling viaHandlerThread - Publisher (
fp_rust::publisher) — pub/sub with optional handler-backed delivery - FP helpers (
fp_rust::fp) —compose!,pipe!,map!,reduce!,filter!,foldl!,foldr!, and related macros - Sync primitives (
fp_rust::sync,fp_rust::handler) —BlockingQueue,HandlerThread,WillAsync,CountDownLatch - Cor (
fp_rust::cor) — Pythonic-generator-style coroutines withyield/yield_fromanddo_m!do-notation macros - Actor (
fp_rust::actor) — lightweight actor model withContext, parent/child messaging, and ask-style patterns - Maybe (
fp_rust::maybe) — optional/monad helpers
Some public APIs are easy to miss. See docs/PROJECT_NOTES.md for the full catalog, origins, and scenarios.
- Scheduler routing —
MonadIO::observe_on/subscribe_on(RxJava-style thread-hopping). Trap: with noobserve_on,subscribe_onis a silent no-op and the effect runs on the caller. Setobserve_onfirst. - Push→pull bridge —
Publisher::as_blocking_queue/subscribe_blocking_queueforward published values into aBlockingQueuefor deterministic pulling;Publisher::subscribe_ondelivers off-thread. - Sync↔async bridge —
BlockingQueue::{take,poll}_result_as_future(for_futures).awaita blocking queue. - Pattern do-notation —
do_m_pattern!extendsdo_m!with typedlet, reassignment,exec, andret. - Utility macros —
map_insert!(bulkHashMapfill),cor_newmutex_and_start!/cor_yield!(coroutine building),contains!,reverse!.
Pattern matching macros or DSL support are not planned for the current roadmap. They are explicitly deferred, not silently removed features.
Read these before relying on stop/shutdown semantics or coroutine scheduling in production code.
Actor, Handler, and Cor each expose stop() (and related lifecycle flags) with ad hoc, per-type semantics. A unified graceful-shutdown design (ordering guarantees, in-flight work draining, parent/child teardown) is deferred and blocked on design. Do not assume Akka- or tokio-like shutdown contracts until that work lands. See ROADMAP.md.
Cor defaults to async scheduling. Two or more coroutines that yield to each other while both in sync mode can deadlock waiting on each other. The safe pattern used in tests and examples:
- Keep the entry coroutine sync if you need synchronous control flow.
- Keep coroutines that are yield targets async (
set_async(true)).
cor_start!, set_async, and cor_yield_from! document this invariant in src/cor.rs.
Some legacy snippets used fixed thread::sleep delays to “wait” for async handlers, actors, or publishers. That is not a synchronization primitive — it races under load. Prefer CountDownLatch, BlockingQueue::take / take_result, or LinkedListAsync polling with timeouts (as in the actor_ask example and current tests) instead of sleeping and hoping.
Runnable examples live under examples/. They require the async/futures stack:
cargo run --example <name> --features=test_runtime| Example | Demonstrates |
|---|---|
monadio |
MonadIO sync/async subscribe, handler scheduling |
publisher |
Publisher pub/sub with handlers |
cor |
Cor yield / yield_from and the sync/async deadlock invariant |
do_notation |
do_m! do-notation over coroutines |
fp |
compose!, pipe!, map! / reduce! / filter! pipelines |
actor |
Actor spawn, parent/child messaging, shutdown messages |
actor_ask |
Ask-style replies via LinkedListAsync and BlockingQueue |
scheduler |
MonadIO observe_on / subscribe_on thread-hopping (and the subscribe_on-alone no-op trap) |
publisher_queue |
Publisher::as_blocking_queue push→pull bridge |
To run the full test suite with futures enabled:
cargo test --features=test_runtime- API reference: docs.rs/fp_rust
- Changelog: CHANGELOG.md
- Roadmap and backlog: ROADMAP.md
- Project notes (origins, lineage, less-known APIs): docs/PROJECT_NOTES.md
MIT — see LICENSE.