Skip to content

TeaEntityLab/fpRust

Repository files navigation

fpRust

tag Crates.io docs.rs CI

license stars forks

Monad and functional-programming utilities for Rust — Rx-style streams, coroutines, actors, and small FP helpers.

Why

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.

Feature flags

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).

Capabilities

  • MonadIO (fp_rust::monadio) — map / fmap / subscribe, sync and async scheduling via HandlerThread
  • 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 with yield / yield_from and do_m! do-notation macros
  • Actor (fp_rust::actor) — lightweight actor model with Context, parent/child messaging, and ask-style patterns
  • Maybe (fp_rust::maybe) — optional/monad helpers

Less-known / advanced capabilities

Some public APIs are easy to miss. See docs/PROJECT_NOTES.md for the full catalog, origins, and scenarios.

  • Scheduler routingMonadIO::observe_on / subscribe_on (RxJava-style thread-hopping). Trap: with no observe_on, subscribe_on is a silent no-op and the effect runs on the caller. Set observe_on first.
  • Push→pull bridgePublisher::as_blocking_queue / subscribe_blocking_queue forward published values into a BlockingQueue for deterministic pulling; Publisher::subscribe_on delivers off-thread.
  • Sync↔async bridgeBlockingQueue::{take,poll}_result_as_future (for_futures) .await a blocking queue.
  • Pattern do-notationdo_m_pattern! extends do_m! with typed let, reassignment, exec, and ret.
  • Utility macrosmap_insert! (bulk HashMap fill), cor_newmutex_and_start! / cor_yield! (coroutine building), contains!, reverse!.

Deferred / non-goals

Pattern matching macros or DSL support are not planned for the current roadmap. They are explicitly deferred, not silently removed features.

Known behavior and limitations

Read these before relying on stop/shutdown semantics or coroutine scheduling in production code.

Stop / shutdown redesign — deferred

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 sync deadlock invariant

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.

thread::sleep is not synchronization

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.

Running examples

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

Documentation

License

MIT — see LICENSE.