GLoC
The G is intentional. GLoC started as a hobby project called Godwin's Logic Component,
born from a mission to bring Flutter's legendary BLoC architecture into Rust.
But as it grows to serve the wider open-source community, that G now stands for Global.
One pattern. Universal. Everywhere Rust runs.
A universal business logic architecture for Rust.
What is GLoC?
GLoC is inspired by Flutter's Bloc architecture — but it's its own thing. It separates business logic from presentation in any Rust application and works anywhere Rust runs: web frontends, desktop GUIs, backend servers, CLIs, and embedded targets.
The core abstraction is Reactor — a single unit that owns one slice of domain state,
exposes domain methods that transition it, and carries a built-in reactive stream that
broadcasts every real transition to all subscribers automatically.
┌─────────────────────────────────────────────────────────────┐
│ Without GLoC │ With GLoC │
│─────────────────────────│───────────────────────────────────│
│ Logic tangled in UI │ Reactor owns logic │
│ State scattered │ Single source of truth │
│ Hard to test │ Fully injectable & mockable │
│ Framework-locked │ Web · Desktop · CLI · Embedded │
└─────────────────────────────────────────────────────────────┘
One pattern. Everywhere Rust runs.
Table of Contents
- Concepts
- Installation
- Ecosystem
- Quick Start
- Define State
- Define a Reactor
- Reactive Stream
- Observers
- Dioxus Integration
- Feature Flags
- Project Structure
- Contributing
- License
Concepts
⚛️ Reactor
A Reactor owns one slice of domain state, exposes methods to mutate it, and
carries a built-in GlocStream — a fan-out reactive stream that broadcasts
every real transition to all subscribers automatically.
Unlike Flutter Bloc which has separate Cubit and Bloc types, GLoC has one:
a Reactor supports both direct method calls and event dispatch via fire().
☢️ Neutron (Event)
A Neutron is an immutable event fired at a reactor. The name follows GLoC's nuclear fission theme: a neutron strikes the reactor and causes a reaction.
Any type satisfying Debug + Send + 'static is automatically a Neutron:
reactor.fire;
🔋 State
Any Clone + PartialEq + Debug type is automatically a State. Use #[reactor_state]
to skip writing the derives:
GLoC performs change detection: emitting a value equal to the current state is a no-op — no stream notification, no re-render.
Other primitives
| Concept | Description |
|---|---|
GlocStream |
Built-in fan-out stream on every reactor. Notifies all subscribers on every real transition. |
ListenerHandle |
RAII cancel token returned by every listen() call. Drop to cancel automatically. |
GlocProvider |
Arc<Mutex<R>> wrapper for shared multi-owner reactor access across threads. |
GlocListener |
Typed trait for old → new observation on a specific reactor. |
GlocObserver |
Global hook — sees every reactor in the app. Supports both Debug strings and typed &dyn Any. |
Installation
Note: GLoC has not yet been published to crates.io. Add it as a git dependency for now:
[]
= { = "https://github.com/godwinjk/gloc" }
For Dioxus desktop:
[]
= { = "https://github.com/godwinjk/gloc" }
= { = "https://github.com/godwinjk/gloc" }
= { = "0.7", = ["desktop"] }
With tracing:
[]
= { = "https://github.com/godwinjk/gloc", = ["tracing"] }
= "0.1"
Ecosystem
Official IDE plugins for GLoC — generate reactors, states, and events without boilerplate.
Quick Start
Reactor — direct methods
use ;
// _h dropped → listener cancelled
Reactor — event-driven dispatch
use ;
Shared reactor across owners
When multiple threads or components need to share one reactor, wrap it in GlocProvider:
use ;
use ;
// ... reactor definition ...
Define State
use reactor_state;
// Struct state
// Enum state — great for loading flows
// With extra derives
Define a Reactor
Mode A — bring your own state
use ;
Mode B — let GLoC generate the state struct
use ;
// Macro generates: pub struct ToggleReactorState { pub active: bool }
What the macro generates
| Generated | Description |
|---|---|
impl Reactor |
state(), emit() with change-detection, stream() |
new(initial) |
Constructor + fires GlocObserver::on_create |
fire(neutron) |
Event dispatch — only when neutrons = N is set |
impl Deref<Target = State> |
Access state fields directly: reactor.count |
Attribute options
| Argument | Effect |
|---|---|
state = SomeType |
Mode A — use an existing type as state |
neutrons = SomeType |
Opt-in event dispatch — generates fire(); you write on_event() |
no_new |
Skip new() generation |
Reactive Stream
Every reactor carries a built-in GlocStream. Subscribe to it for fan-out
reactive notifications:
let reactor = new;
// Multiple subscribers — all fire on every emit()
let _h1 = reactor.stream.listen;
let _h2 = reactor.stream.listen;
reactor.increment; // both listeners fire
ListenerHandle — listen() returns a handle. Drop it to cancel:
// _h dropped → listener cancelled
reactor.increment; // silent
Call handle.forget() to keep the listener permanently:
reactor.stream.listen.forget;
Close signal — get notified when a reactor shuts down:
let _h = reactor.stream.on_close;
let provider = new;
provider.release; // → on_close() fires, stream.close() fires callbacks
Reactor-to-reactor — one reactor subscribes to another:
// OrderReactor watches CartReactor
let _h = cart.stream.listen;
// Clean up when cart is gone
let _close = cart.stream.on_close;
Observers
Typed listener — GlocListener
use GlocListener;
;
let provider = new;
let _h = provider.attach_listener;
provider.update; // prints: 0 → 1
Global observer — GlocObserver
Observe every reactor in the app from one place. Two methods for transitions:
use ;
;
Dioxus Integration
gloc-dioxus connects reactors to Dioxus with zero prop drilling.
The stream→signal bridge is automatic — any emit() call updates the Dioxus
signal and schedules a re-render without any manual wiring.
use *;
use ;
use ;
Full showcase with 5 pages:
| Page | Feature |
|---|---|
| /counter | gloc_builder! — rebuilds on every emit |
| /neutrons | gloc_builder!(when:) — rebuild guard + neutron dispatch |
| /theme | gloc_consumer!(build_when:, listen_when:) — both guards |
| /cart | gloc_listener!(when:) — side effect gated on status transition |
| sidebar | Mode B #[reactor] — shared across all pages |
Feature Flags
| Crate | Feature | Effect |
|---|---|---|
gloc |
tracing |
tracing::debug! inside emit() — logs every state transition. Zero cost when disabled. |
Project Structure
GLoC/
├── gloc-core/ Reactor, State, GlocStream, GlocProvider, GlocListener, GlocObserver
├── gloc-macro/ #[reactor], #[reactor_state]
├── gloc/ Umbrella crate
├── gloc-test/ ReactorTester + reactor_test! macro
├── gloc-dioxus/ Dioxus adapter — use_gloc_provide, use_gloc, gloc_builder!
├── gloc-axum/ Axum adapter — AxumReactor, new_axum_state
├── gloc-bevy/ Bevy adapter — GlocPlugin, GlocResource
└── examples/
├── dioxus/ Desktop UI — 5-page feature showcase
├── axum/ HTTP API — CartReactor + InventoryReactor
├── bevy/ Headless game — PlayerReactor + WaveReactor
└── cli/ Terminal REPL — task manager
Contributing
GLoC welcomes contributions of every kind.
The only hard rule: every change must go through a Pull Request and pass CI.
# Clone and branch
# Full local check suite
| CI Job | Local command |
|---|---|
| build | cargo build --workspace |
| test | cargo test --workspace |
| fmt | cargo fmt --all -- --check |
| clippy | cargo clippy --workspace --all-targets -- -D warnings |
License
Licensed under the MIT License.
Built with Rust 🦀 — designed for everyone.
github.com/godwinjk/gloc · crates.io/crates/gloc · docs.rs/gloc