[][src]Module tbot::state

The stateful event loop and utilities for it.

The stateful event loop makes it easier to write bots that depend on some kind of state. It can be a chatting bot, a quiz bot or any other bot that needs to store data. The stateful event loop also can be used to easily share some data between all handlers, e.g. if you use a database that stores all your data, you can implement an utility struct with methods that simplify access to the database and share it across all your handlers.

For example, let's think of a bot with a global counter, and the bot allows increasing and showing it.

If we'd have gone with a stateless event loop, we'd start with this:

let mut bot = tbot::from_env!("BOT_TOKEN").event_loop();

To go with a stateful bot, we simply call stateful_event_loop instead of event_loop:

let mut bot = tbot::from_env!("BOT_TOKEN")
    .stateful_event_loop(initial_state);

What should initial_state be? It can actually be any value (that can be shared across threads). You'd think that, as we only have a counter, we would simply use any integer type. However, tbot won't let you mutate the state — instead, you should make use of interior mutability yourself. This design decision was made to avoid mutability if it isn't needed and for users to decide what parts of their state should be mutable — that is to prevent all the state being locked when only a part of it is actually needed to be locked.

So what we need is an integer wrapped in an RwLock:

use tokio::sync::RwLock;

let mut bot = tbot::from_env!("BOT_TOKEN")
    .stateful_event_loop(RwLock::new(0));

Now, if we would have gone with the stateless event loop, we'd write this:

bot.command("increase", |context| async move { /* .. */ });

Once we opt in to the stateful event loop, we need to write this:

bot.command("increase", |context, state| async move { /* .. */ });

The state is passed being wrapped in an Arc, that is, this handler receives Arc<RwLock<i32>> as the second argument. This allows concurrent access to the state. Now you only need to use the state:

use tbot::prelude::*;

bot.command("increase", |context, state| async move {
    *state.write().await += 1;
    let call_result =
        context.send_message("Increased the counter").call().await;

    if let Err(err) = call_result {
        dbg!(err);
    }
});

tbot also provides a few utility state stores for common patterns. You can combine them with other state stores or with your state if needed.

Re-exports

pub use chats::Chats;
pub use messages::Messages;

Modules

chats

A store for state per chat.

messages

A store for state per message.

Structs

StatefulEventLoop

A stateful event loop.