Last updated: April 13, 2025
Table of Contents
1. Introduction: Rust and In-Memory Data Stores
Redis is an extremely popular open-source, in-memory data structure store, often used as a high-performance cache, message broker, and database. Its speed stems from keeping data primarily in RAM. When building applications in Rust, you often need to interact with Redis for caching, session management, real-time leaderboards, or simple key-value storage.
The redis-rs
crate is the most widely used and maintained Rust client library for Redis. It
provides both synchronous and asynchronous APIs for interacting with a Redis server.
This guide covers the basics of setting up redis-rs
, connecting to a Redis instance, and
performing common operations like setting and getting values, using both the synchronous and asynchronous
(Tokio-based) interfaces.
2. The redis-rs
Crate
redis-rs
provides a convenient and type-safe way to send Redis commands and handle responses.
Key features include:
- Support for a wide range of Redis commands.
- Both synchronous and asynchronous (via feature flags) clients.
- Connection pooling (optional feature).
- Support for Redis Cluster, Sentinel, Pipelining, and Pub/Sub.
- Conversion between Redis replies and Rust types.
3. Prerequisites
- Rust and Cargo installed (see Getting Started with Rust).
- A running Redis instance. The easiest way locally is via Docker:
This starts Redis and exposes the default portdocker run -d --name my-redis -p 6379:6379 redis:alpine
6379
. - Basic understanding of Redis commands (like
SET
,GET
). - (For async section) Familiarity with Rust's
async/await
and Tokio.
4. Project Setup
Create a new Rust binary project:
cargo new rust_redis_example
cd rust_redis_example
Add the redis
crate to your Cargo.toml
:
[package]
name = "rust_redis_example"
version = "0.1.0"
edition = "2021"
[dependencies]
redis = { version = "0.25", features = [] } # Check crates.io for latest version
# Add features later for async or pooling
Or use cargo add
:
cargo add redis@0.25
We start with no extra features enabled for the synchronous examples.
5. Synchronous Usage
The default client provided by redis-rs
is synchronous.
5.1 Connecting Synchronously
You create a Client
and then get a Connection
from it.
use redis::Commands; // Trait needed for commands like set/get
fn connect_sync() -> redis::RedisResult {
let redis_url = "redis://127.0.0.1:6379/"; // Default Redis URL
let client = redis::Client::open(redis_url)?; // Create client
let connection = client.get_connection()?; // Get synchronous connection
println!("Synchronously connected to Redis!");
Ok(connection)
}
Note the use of redis::RedisResult
and the ?
operator for error handling.
5.2 Running Basic Commands (SET, GET)
Once you have a connection, you can execute Redis commands using methods provided by traits like
redis::Commands
.
// Inside a function that has `mut connection: redis::Connection`
// Use the `Commands` trait methods
// redis::cmd("SET").arg("my_key").arg("my_value").query(&mut connection)?; // Generic command
// Use convenience methods from the Commands trait
let _: () = connection.set("user:1:name", "Alice")?; // SET command
println!("Set user:1:name to Alice");
let name: String = connection.get("user:1:name")?; // GET command
println!("Retrieved user:1:name: {}", name);
// Get a non-existent key (returns RedisError NotFound)
match connection.get::<_, String>("non_existent_key") {
Ok(value) => println!("Unexpected value: {}", value),
Err(e) => {
if e.kind() == redis::ErrorKind::TypeError { // Type error indicates key not found for String conversion
println!("Key 'non_existent_key' not found (as expected).");
} else {
eprintln!("Error getting non_existent_key: {}", e);
}
}
}
Commands like set
and get
are generic and require type annotations (like
let name: String = ...
or connection.get::<_, String>(...)
) so Rust knows what type
to expect from Redis.
5.3 Complete Synchronous Example
Combine connection and commands in src/main.rs
:
use redis::{Client, Commands, Connection, RedisResult};
// Connect function from above
fn connect_sync() -> RedisResult {
let redis_url = "redis://127.0.0.1:6379/";
let client = Client::open(redis_url)?;
let connection = client.get_connection()?;
println!("Synchronously connected to Redis!");
Ok(connection)
}
// Function to run Redis commands
fn run_redis_commands(connection: &mut Connection) -> RedisResult<()> {
// SET command
let _: () = connection.set("sync_key", "Hello Sync Redis!")?;
println!("SET sync_key");
// GET command
let value: String = connection.get("sync_key")?;
println!("GET sync_key: {}", value);
// INCR command
let new_count: i32 = connection.incr("counter", 1)?; // Increment counter by 1
println!("INCR counter: new value is {}", new_count);
Ok(())
}
fn main() {
println!("Running synchronous Redis example...");
match connect_sync() {
Ok(mut conn) => { // Connection needs to be mutable to run commands
if let Err(e) = run_redis_commands(&mut conn) {
eprintln!("Error running Redis commands: {}", e);
}
}
Err(e) => {
eprintln!("Error connecting to Redis: {}", e);
}
}
}
Run with cargo run
(ensure Redis is running via Docker).
6. Asynchronous Usage with Tokio
For non-blocking applications, especially web servers, you'll want to use the asynchronous API.
6.1 Enabling Async Feature
First, enable the necessary features for redis-rs
in Cargo.toml
:
[dependencies]
# Enable tokio-comp feature for redis
redis = { version = "0.25", features = ["tokio-comp"] }
tokio = { version = "1", features = ["full"] }
# Add other needed dependencies like anyhow or serde if needed
anyhow = "1.0"
Or use cargo add
:
cargo add redis@0.25 --features tokio-comp
cargo add tokio --features full
cargo add anyhow # For easier error handling
The tokio-comp
feature enables compatibility with the Tokio runtime.
6.2 Connecting Asynchronously
Connecting asynchronously involves getting an AsyncConnection
.
use redis::aio::Connection; // Use redis::aio for async
use redis::{Client, RedisResult};
use anyhow::Result; // Using anyhow for main function Result
async fn connect_async() -> RedisResult {
let redis_url = "redis://127.0.0.1:6379/";
let client = Client::open(redis_url)?;
// Get asynchronous connection
let connection = client.get_async_connection().await?;
println!("Asynchronously connected to Redis!");
Ok(connection)
}
6.3 Running Commands Asynchronously
Commands are run using the same Commands
trait methods, but now they need to be
.await
ed.
// Inside an async function with `mut connection: redis::aio::Connection`
// use redis::AsyncCommands; // Trait needed for async set/get
// Use convenience methods from AsyncCommands trait
// Note: connection must be mutable here too
let _: () = connection.set("async_key", "Hello Async Redis!").await?;
println!("SET async_key");
let value: String = connection.get("async_key").await?;
println!("GET async_key: {}", value);
let new_count: i32 = connection.incr("async_counter", 1).await?;
println!("INCR async_counter: new value is {}", new_count);
6.4 Complete Asynchronous Example
Modify src/main.rs
for async operations:
use redis::aio::Connection;
use redis::{AsyncCommands, Client, RedisResult}; // Import AsyncCommands
use anyhow::Result;
// Async connect function from above
async fn connect_async() -> RedisResult {
let redis_url = "redis://127.0.0.1:6379/";
let client = Client::open(redis_url)?;
let connection = client.get_async_connection().await?;
println!("Asynchronously connected to Redis!");
Ok(connection)
}
// Async function to run Redis commands
async fn run_redis_commands_async(connection: &mut Connection) -> RedisResult<()> {
// SET command
let _: () = connection.set("async_key", "Value set asynchronously").await?;
println!("SET async_key");
// GET command
let value: String = connection.get("async_key").await?;
println!("GET async_key: {}", value);
// INCR command
let new_count: i32 = connection.incr("async_counter", 1).await?;
println!("INCR async_counter: new value is {}", new_count);
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> { // Use anyhow::Result for convenience
println!("Running asynchronous Redis example...");
match connect_async().await {
Ok(mut conn) => { // Connection needs to be mutable
if let Err(e) = run_redis_commands_async(&mut conn).await {
eprintln!("Error running async Redis commands: {}", e);
// Convert redis::RedisError to anyhow::Error if needed, or handle specifically
return Err(e.into());
}
}
Err(e) => {
eprintln!("Error connecting async to Redis: {}", e);
return Err(e.into()); // Propagate error as anyhow::Error
}
}
Ok(())
}
Run this with cargo run
.
7. Error Handling
Most redis-rs
functions return redis::RedisResult
, which is an alias for
Result
. Handle these errors using:
?
operator (within functions returningResult
).match
statements to handle specificredis::ErrorKind
values.unwrap()
orexpect()
(use cautiously, as they panic on error).- Error handling crates like
anyhow
orthiserror
for more structured error management.
8. Conclusion
The redis-rs
crate provides a comprehensive and flexible interface for interacting with Redis
from Rust. Whether you need simple synchronous operations or high-performance asynchronous communication using
Tokio, the library offers a clear API based on standard Redis commands. Remember to handle potential errors
appropriately using Rust's Result
type and consider the asynchronous API for non-blocking
applications like web services.
9. Related Articles
- Getting Started with Rust
- Writing Concurrent Applications in Rust
- Error Handling in Rust
- SQL vs. NoSQL Databases
- Docker Commands Guide (for running Redis)
- Common Rust Crates for Web Development