futures-signals: reactive values in Rust without a runtime tax
Zero-cost functional reactive Signals for Rust
At a glance
- What is it?
- futures-signals is a Rust crate that models a value that changes over time and lets other parts of your program react to those changes, built on the futures ecosystem. It is a small, focused library with no releases tagged and a last push in April 2026, so adoption means depending on a low churn crate that mostly compiles away.
- Who is it for?
- Adopt futures-signals if you are already inside the futures ecosystem and want a small reactive primitive that stays out of your runtime profile, particularly for state synchronisation, GUI updates or automatic persistence. Do not adopt it if you need a mature, semver-stable library with tagged releases and a documented compatibility story, because the crate has no GitHub releases and the last push was on 2026-04-07.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A signal is a value that changes over time, and that is the whole abstraction
The idea underneath futures-signals is small enough to state in one line and general enough to spread through an application. A Signal is a value that changes over time, and you can be efficiently notified whenever its value changes. That is the entire contract. Everything else in the crate exists to make that contract composable in a way that fits Rust async code. The README lists the situations it is meant for, and the list is worth reading closely because it reveals where the abstraction pays off. You can automatically serialize your program's state to a database whenever it changes. You can send a message to the server whenever the client's state changes, or the other way around, which the README describes as keeping client and server state in sync. A game engine can update its state automatically when something changes. Continuous input such as the current temperature or the current time can be represented as a Signal. A GUI can be updated whenever the state behind it changes, so the two never drift apart. The README also points at the dominator crate for building web apps that stay in sync with program state. What all of these share is the same shape: one source of truth, many observers, and no polling loop.
Zero-cost means the features you do not use cost nothing at runtime
The README makes a performance claim and explains it, which is unusual and useful. The claim is that if you do not use a feature you do not pay any performance cost, and the features that you do use are as fast as possible. The mechanism behind that claim is visible in the crate's dependency list. It is built on the futures ecosystem, pulling in futures-core, futures-channel and futures-util at 0.3.0, plus discard and pin-project. Those are the pieces of the futures stack that carry the actual machinery, and the crate depends on them rather than reimplementing channels, pinning and task handling. The claim is also confirmed by the feature flags. The Cargo.toml lists two optional dependencies, log and serde, both behind named features, and the default set is `default = ["debug", "serde"]`. So the zero-cost framing has a concrete reading: the crate compiles down to the parts you use. Note the default set, though, because debug is enabled by default and it is what pulls in log. The crate does not define a `std` or `no_std` split in this manifest, so there is no obvious no-std story here. The performance argument is also not backed by any published benchmark numbers in the repository listing, so treat it as a design intent and a reasonable expectation for a thin wrapper over futures primitives, not as a measured figure.
The crate is published as futures-signals, not rust-signals
The repository is Pauan/rust-signals, but the crate you consume has a different name, and this trips people up. The Cargo.toml is explicit:
name = "futures-signals"
version = "0.3.34"
authors = ["Pauan <[email protected]>"]
description = "Zero cost FRP signals using the futures crate"
repository = "https://github.com/Pauan/rust-signals"
license = "MIT"
edition = "2018"So the dependency name in your own manifest is `futures-signals` while the source lives in the rust-signals repository, and the README badges point at crates.io/crates/futures-signals and docs.rs/futures-signals. The author is Pauan. The crate is licensed MIT, so it can be used in closed source products, though you still owe the license text and copyright notice in your distribution. The README describes the crate as providing functional reactive programming Signals, and the keywords in the manifest are signal, reactive, frp and futures, with categories covering asynchronous work, concurrency and data structures. Edition 2018 means the crate compiles on an older Rust edition than the current default, which is usually a non-issue but does mean the crate does not assume newer edition idioms.
Getting the crate and reading the tutorial the README points to
The README does not print an installation command. It states the best way to learn more is to read the tutorial, hosted on docs.rs for futures-signals, and the badges at the top link to the crates.io listing and the generated API docs. The dependency set that a project would add is visible in the manifest, with the core futures pieces and the two optional extras:
[features]
default = ["debug", "serde"]
debug = ["log"]
[dependencies]
futures-core = "0.3.0"
futures-channel = "0.3.0"
futures-util = "0.3.0"
discard = "1.0.3"
pin-project = "1.0.2"Three things are worth checking before you wire the crate into a project. First, the version of the crate itself, which is 0.3.34, is still below 1.0, so the usual semver stability guarantees do not apply and you should pin it exactly if you cannot absorb breaking changes. Second, the `serde` feature is on by default and pulls serde with its derive feature, which is convenient if you plan to serialize signal state, and dead weight if you do not. Third, the `debug` feature is also on by default and enables the `log` crate, so a release build that you did not intend to instrument still carries the logging dependency unless you set `default-features = false`. The README does not show the cargo invocation for that, so read the feature section of the crate docs before assuming the effect. Everything else in the setup story is the tutorial's job, and the README is candid that the tutorial is where the learning is.
No GitHub releases means you track the changelog and the version number
The maintenance picture is the part to weigh most carefully here, and the facts are thin. The repository has no GitHub releases, so there is no v0.3.34 tag to point a team at and no release notes attached to a release entry. What exists is the Cargo.toml version field and a CHANGELOG.md at the top level of the repository, which is where the history is recorded instead. The last push was on 2026-04-07, which is more than six months before today, so this is not a crate that is being actively developed week to week. That does not make it unsafe. A small, self-contained library that rarely changes is arguably easier to depend on than a busy one, and the MIT licence and a plain dependency set keep the supply chain surface small. But you should plan for the possibility that a breaking change arrives in a minor version bump, because there is no 1.0 promise behind the version number and no release tag discipline to slow that down. For a company with a strict change advisory board, a crate with no published release history is friction, and you should either vendor the source into your tree or pin the exact version and accept that you own the update. The repository layout is small, with src, tests, benches and the Cargo.toml, which makes vendoring realistic rather than hypothetical.
Where reactive signals are the wrong abstraction
Signals earn their keep when one value has many independent consumers that must all stay consistent, and when the value changes at unpredictable moments. They are a poor fit in several common situations. If the flow of data is naturally sequential and each stage consumes the previous output, a channel or a plain future is clearer and you pay less. If your state changes rarely and you only ever read it at specific moments, a signal adds a dependency and a subscription model for no benefit. If your application is built around a request-response flow with strict ordering and you need to observe every intermediate value, signals may hide the intermediate transitions you wanted to see, since the model is built around the current value rather than a history of values. There is also a sharp edge around the async runtime: the crate is built on futures, so it assumes you are in an async context with a runtime that can drive the futures you create, and it does not itself pick one for you. For a synchronous crate where you just want an observer list, this is too much machinery. The README does not describe a runtime-agnostic or thread-safe guarantee, so if you plan to mutate a signal from another thread, verify that behaviour against the API docs before committing to the design.
Compared with channels and with a GUI framework's own state model
The alternative most Rust developers already carry in their pocket is the futures channel. A channel is a queue: one producer, many consumers, each message delivered once per receiver, and the receiver has to poll for it. A signal is closer to a shared cell that notifies: every observer is told that the value changed, and each one can then read the current value rather than reconstruct it from a stream of deltas. That difference matters for the use cases the README names. When a client must send a message to the server whenever its state changes, a channel works only if every consumer needs every update. When the goal is that a GUI, a database writer and a network sender all agree on one current value, the signal's pull-the-current-value model removes the ordering problem of three separate subscriptions consuming from three separate queues. The other alternative is a GUI framework's built-in reactivity, where the framework already owns the state and the update mechanism. futures-signals is the pick when your state is not primarily a GUI tree, when the same value also has to be persisted or sent over the network, and when you want that coordination to live in your own code rather than in the framework's model. For a pure desktop UI with a framework that already handles it, the framework's model is less machinery.
Editorial conclusion
Adopt futures-signals if you are already inside the futures ecosystem and want a small reactive primitive that stays out of your runtime profile, particularly for state synchronisation, GUI updates or automatic persistence. Do not adopt it if you need a mature, semver-stable library with tagged releases and a documented compatibility story, because the crate has no GitHub releases and the last push was on 2026-04-07. Before committing, check the default feature set, since `debug` and `serde` are on by default, read the tutorial linked from the README at docs.rs, and confirm that the `dominator` integration the README mentions is something you actually need.
Frequently asked questions
What is futures-signals and what problem does it solve?
It is a Rust crate providing functional reactive programming Signals built on top of the futures crate. A Signal is a value that changes over time, and observers are notified when it changes, which lets one piece of state drive a database write, a network message or a GUI update.
How do I add futures-signals to a Rust project?
The README does not print an install command and points to the tutorial on docs.rs for futures-signals instead. The crate version is 0.3.34 and it is published on crates.io under the name futures-signals, while its source lives in the Pauan/rust-signals repository.
Does futures-signals require serde or log?
Both are optional dependencies controlled by features. The manifest sets `default = ["debug", "serde"]`, with `debug = ["log"]`, so a default build enables both, and you would need to turn off default features to avoid the serde and log dependencies.
Is futures-signals actively maintained?
The repository has no GitHub releases, and the last push was on 2026-04-07, so changes are infrequent rather than continuous. Version numbers live in Cargo.toml and history is recorded in the CHANGELOG.md at the top level of the repository.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pauan-rust-signals)Was this useful?