DEV Community

Mykhailo Krainik
Mykhailo Krainik

Posted on

BLoC (Business Logic Component) in Rust

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;
}
Enter fullscreen mode Exit fullscreen mode

Output:

State change = CounterState { value: 1 }
State change = CounterState { value: 2 }
State change = CounterState { value: 1 }
Enter fullscreen mode Exit fullscreen mode

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)