I’ve heard a lot about the BLoC design pattern from the Dart and Flutter world, and I was curious how it might translate to Rust. Particularly in cases where changes in one part of the system need to trigger updates in others, without explicitly calling functions each time
use tokio::sync::{mpsc, broadcast};
use tokio::task;
#[derive(Debug, Clone)]
enum CounterEvent { Inc, Dec, Reset }
#[derive(Debug, Clone, Copy)]
struct CounterState { value: i64 }
struct CounterBloc {
// Send events into the bloc:
pub events: mpsc::Sender<CounterEvent>,
// Subscribe to states from the bloc:
pub states: broadcast::Receiver<CounterState>,
}
impl CounterBloc {
fn new() -> Self {
let (tx_evt, mut rx_evt) = mpsc::channel::<CounterEvent>(64);
let (tx_state, rx_state) = broadcast::channel::<CounterState>(64);
// initial state
let mut state = CounterState { value: 0 };
// publish initial state (optional)
let _ = tx_state.send(state);
// BLoC
task::spawn({
let tx_state = tx_state.clone();
async move {
while let Some(evt) = rx_evt.recv().await {
match evt {
CounterEvent::Inc => state.value += 1,
CounterEvent::Dec => state.value -= 1,
CounterEvent::Reset => state.value = 0,
}
let _ = tx_state.send(state); // broadcast new state
}
}
});
Self { events: tx_evt, states: rx_state }
}
}
#[tokio::main]
async fn main() {
let bloc = CounterBloc::new();
// Receiver and react to state changes:
let mut state_rx = bloc.states.resubscribe();
tokio::spawn(async move {
while let Ok(s) = state_rx.recv().await {
println!("State change = {:?}", s);
}
});
// Simulate UI intents:
let _ = bloc.events.send(CounterEvent::Inc).await;
let _ = bloc.events.send(CounterEvent::Inc).await;
let _ = bloc.events.send(CounterEvent::Dec).await;
}
Output:
State change = CounterState { value: 1 }
State change = CounterState { value: 2 }
State change = CounterState { value: 1 }
Also this might be especially useful in UIs built with Dioxus or Tauri, where changes are expected to trigger immediate reactions across the system.
Top comments (0)