Expand description
rustis is a Redis client for Rust.
§Philosophy
- Low allocations
- Full async library
- Lock free implementation
- Rust idiomatic API
§Features
- Support all documented Redis Commands up to and including Redis 8.8
- Async support (tokio)
- Different client types:
- Single client
- Multiplexed client
- Pooled client manager (based on bb8)
- Automatic command batching
- Advanced reconnection & retry strategy
- Pipelining support
- Configuration with Redis URL or dedicated builder
- TLS support
- Transaction support
- Pub/sub support
- Sentinel support
- LUA Scripts/Functions support
- Cluster support
- Client-side caching support
§Optional Features
| Feature | Description |
|---|---|
tokio-runtime | Tokio runtime (default) |
tokio-rustls | Tokio Rustls TLS support |
tokio-native-tls | Tokio native_tls TLS support |
json | Enables JSON (de)serialization support via serde_json |
client-cache | Enables client-side caching support |
pool | Pooled client manager |
tokio-rustls and tokio-native-tls are mutually exclusive: enabling both is a
compile error. Each implies the corresponding backend-only feature (rustls,
native-tls), which gates the TLS configuration types. Enabling a backend-only
feature on its own is also a compile error: it brings the configuration types
without the connection code that honours them.
pool puts bb8 in rustis’ public API: PooledClientManager
implements bb8::ManageConnection and the crate is re-exported as bb8. A bb8 major
release is therefore a breaking rustis release even when no rustis code changes, and two
crates in one dependency graph cannot disagree about the bb8 version. That is the price of
configuring the pool with bb8’s own builder rather than through a wrapper.
The remaining features are for developing rustis itself and carry no stability
guarantee: bench (exposes internal RESP entry points to the benchmark crates, as
resp::bench_support), fuzzing (same, for the cargo-fuzz targets in fuzz/) and
web-examples (the axum / actix-web examples). None of the three carries a
dependency: the crates they need are dev dependencies, so a dependent that enables one
still builds nothing extra.
§Protocol Compatibility
Rustis uses the RESP3 protocol exclusively.
The HELLO 3 command is automatically sent when establishing a connection.
Therefore, your Redis server must support RESP3 (Redis ≥6.0+ with RESP3 enabled).
If you use Redis 5 or older, or your Redis 6+ server still defaults to RESP2, Rustis will not work.
To verify your server supports RESP3:
redis-cli --raw HELLO 3If you see server info (role, version, etc.), you’re good to go. If you get an error, upgrade Redis.
§Basic Usage
use rustis::{
client::Client,
commands::{FlushingMode, ServerCommands, StringCommands},
Result,
};
#[tokio::main]
async fn main() -> Result<()> {
// Connect the client to a Redis server from its IP and port
let client = Client::connect("127.0.0.1:6379").await?;
// Flush all existing data in Redis
client.flushdb(FlushingMode::Sync).await?;
// sends the command SET to Redis. This command is defined in the StringCommands trait
client.set("key", "value").await?;
// sends the command GET to Redis. This command is defined in the StringCommands trait
let value: String = client.get("key").await?;
println!("value: {value:?}");
Ok(())
}Each command family is a trait, so the import block grows with the number of
families a program calls. The prelude re-exports all of them, together with
the executors and the pub/sub types, which shortens the block above to
use rustis::{commands::FlushingMode, prelude::*, Result};.
§Client
See the module client to discover which are the 3
usages of the Client struct and how to configure it.
You will also learn how to use pipeline, pub/sub and transactions.
§RESP
RESP is the Redis Serialization Protocol.
See the module resp to discover how rustis
allows programmers to communicate with Redis in a Rust idiomatic way.
You will learn how to:
- Manipulate the rustis object model, the enum
Value, which is a generic Rust data structure over RESP. - Convert Rust types into Rust Commands with the
Commandstruct, whose arguments are any type implementing serde’sSerialize. - Convert Rust command responses into Rust type with serde and helpful marker traits.
§Commands
In order to send Commands to the Redis server, rustis offers two API levels:
- High-level Built-in commands that implement all documented Redis commands up to and including Redis 8.8, plus the Redis Stack commands.
- Low-level Generic command API to express any request that may not exist in rustis:
- new official commands not yet implemented by rustis.
- commands exposed by additional Redis modules not included in Redis Stack.
§Built-in commands
See the module commands to discover how Redis built-in commands are organized in different traits.
§Generic command API
To use the generic command API, you can use the cmd function to specify the name of the command,
followed by one or multiple calls to CommandBuilder::arg to add arguments to the command,
and to CommandBuilder::key to add arguments that are Redis keys.
This command can then be passed as a parameter to one of the following associated functions, depending on the client, transaction or pipeline struct used:
sendsend_and_forgetPipeline::queue_command, to batch several of them
use rustis::{client::Client, resp::cmd, Result};
#[tokio::main]
async fn main() -> Result<()> {
let client = Client::connect("127.0.0.1:6379").await?;
client
.send::<()>(
cmd("MSET")
.key("{my}key1")
.arg("value1")
.key("{my}key2")
.arg("value2")
.key("{my}key3")
.arg("value3")
.key("{my}key4")
.arg("value4"),
None,
)
.await?;
let values: Vec<String> = client
.send(
cmd("MGET")
.key("{my}key1")
.key("{my}key2")
.key("{my}key3")
.key("{my}key4"),
None,
)
.await?;
assert_eq!(vec!["value1", "value2", "value3", "value4"], values);
Ok(())
}§Warning: keys must be added with key, not arg
Only arguments added with key take part in Cluster slot
computation. A command built with arg alone carries no slot and is sent to a random node
of the cluster, with no error to tell you: a single-key command gets a MOVED reply, and the
retry that follows the topology refresh picks a random node again. A multi-key command such as
MSET fails with CROSSSLOT.
A multi-key command additionally requires all its keys to hash to the same slot, which is what
the {my} hash tag guarantees in the example above.
This does not apply to the strongly typed command API (commands): those functions already
mark their keys.
§Adding a command family of your own
The generic API sends anything, but it costs the fluent shape. Add a missing command on the
same footing as the built-in ones with prepare_command:
use rustis::{
client::{Client, PreparedCommand, prepare_command},
resp::cmd,
};
use serde::Serialize;
trait MyCommands<'a> {
#[must_use]
fn myget(self, key: impl Serialize) -> PreparedCommand<'a, Self, String>
where
Self: Sized,
{
prepare_command(self, cmd("MYGET").key(key))
}
}
impl<'a> MyCommands<'a> for &'a Client {}Every trait in commands is written this way. Implement it for
Pipeline and Transaction too to
queue the command into a batch.
§Warning: raw bytes need an adapter type
client.set("key", b"val") compiles and fails at runtime: serde serializes &[u8] and
Vec<u8> as sequences of integers, not as one bulk string. Wrap them in
RefBulkString or BulkString. See the
resp module page for the reason.
§Errors
Every fallible call returns Result<T>, whose error is Error.
An Error is what went wrong, kind(), plus the command it belongs
to, command():
use rustis::{Error, ErrorKind, Result};
fn report(result: Result<String>) {
if let Err(e) = result {
match e.kind() {
ErrorKind::Timeout(_) => eprintln!("{:?} timed out", e.command()),
ErrorKind::Redis(redis_error) => eprintln!("the server refused it: {redis_error}"),
_ => eprintln!("{e}"),
}
}
}The command matters because a client multiplexes: a single connection carries
hundreds of commands at once, so a bare “the operation timed out” names nothing
the application can act on. It is set for every error the client raises on behalf
of a command, and absent for the ones raised outside any — a connection timeout,
for instance. Display appends it, so a logged error reads
The I/O operation's timeout expired (while executing BLMPOP).
§Client-side caching
See the module cache to discover how you can implement client-side caching.
Re-exports§
pub use bb8;pool
Modules§
- cache
client-cache - Client-side caching support
- client
- Defines types related to the clients structs and their dependencies:
Client,ExclusiveClient,PooledClientManager,Pipeline,Transactionand how to configure them - commands
- Define Redis built-in commands in a set of traits
- prelude
- The traits and types a program needs in scope, in one
use. - resp
- Defines types related to the
RESPprotocol and their encoding/decoding
Structs§
- Error
- Any error raised by the client, and the command it belongs to.
- Error
Context - Identifies the command an
Errorbelongs to. - Redis
Error - Error issued by the Redis server
Enums§
- Client
Error - Errors issued by the client
- Error
Kind - What an
Erroris, independently of the command it belongs to. - Redis
Error Kind - Redis server error kind
- Timeout
Kind - Which deadline expired, in an
ErrorKind::Timeout.