Performance
The framework is a thin layer on top of reqwest and tokio-tungstenite; the things that move the needle are gateway-side knobs (shards and intents) and how you share session API handles / BotApi between tasks. Generic Rust performance advice doesn't belong here.
Narrow your intents
Every Intents flag you enable adds another category of dispatch the gateway has to push and your handler has to ignore (or handle). Intents::default() is a convenient public preset; for non-trivial bots, replace it with the explicit set you actually consume:
let intents = Intents::new()
.with_public_guild_messages()
.with_direct_message();The savings come both from gateway bandwidth and from skipping deserialization for events you don't want.
Sharding
Client::start() reads the recommended shard count from BotApi::get_gateway (gateway_info.shards). Each shard is a separate WebSocket connection driven by the session manager. You don't normally configure this — the count comes from QQ's side and reflects your bot's guild population.
For very small bots a single shard is fine and uses the least resource. For larger bots, the session manager opens multiple shards in sequence, throttled by Gateway::session_start_interval(max_concurrency), so you don't trip the daily session-start budget. If your handler is CPU-light but you have many guilds, increasing concurrency on QQ's side (which you negotiate via gateway_info.session_start_limit.max_concurrency) reduces the spread between shards coming online.
The reconnect throttle is implemented as round(2 / max_concurrency) with a 1-second floor; do not bypass it. See gateway for the details.
Share Session API Handles
When you spawn work from inside a handler, move session.api_handle() into the task:
async fn message_create(&self, session: ChannelReplySession) {
let api = session.api_handle();
tokio::spawn(async move {
let _ = api.send_message("channel", params).await;
});
}The clone is cheap relative to a single HTTP request. Don't build a new BotApi per call — the reqwest::Client it wraps maintains its own connection pool, and rebuilding it throws that pool away.
Keep handlers non-blocking
Handler calls are awaited serially per shard. Long-running synchronous work in a handler delays the next event for that shard. Either keep the work small or tokio::spawn it as shown above. The framework gives you no other backpressure mechanism — the event channel is unbounded and will grow if your handler can't keep up.
HTTP timeout
The default HttpClient timeout is 30 seconds (botrs::DEFAULT_TIMEOUT). Lower it with Client::with_config(... , timeout_secs, ...) if your latency budget is tighter than that, but remember that uploads and large-payload responses can legitimately take time. A too-aggressive timeout produces BotError::Timeout flapping rather than performance.