The Startup
Published in

The Startup

Getting Started With Rust and Redis

A hands-on tutorial on how to use Redis with the Rust programming language

Photo by Danial RiCaRoS on Unsplash

Pre-requisites

You will need Rust (version 1.39 or above) installed on your computer. If you intend to use Azure Cache for Redis, simply create a free Azure subscription, and setup an Azure Cache for Redis instance using the Azure portal.

docker run --rm -p 6379:6379 redis

Code walk through

For you to get a better understanding, this section covers a step-by-step walk through of the code. It covers all the functions, each of which covers a specific Redis data structure. It is followed by the Run the sample application section.

Connect to Redis

The connect function is used to establish a connection to Redis. It expects host name and the password to be passed in as environment variables REDIS_HOSTNAME and REDIS_PASSWORD respectively. The format for the connection URL is <uri scheme>://<username>:<password>@<hostname>.

fn connect() -> redis::Connection {
//format - host:port
let redis_host_name =
env::var("REDIS_HOSTNAME").expect("missing environment variable REDIS_HOSTNAME");

let redis_password = env::var("REDIS_PASSWORD").unwrap_or_default();
//if Redis server needs secure connection
let uri_scheme = match env::var("IS_TLS") {
Ok(_) => "rediss",
Err(_) => "redis",
};
let redis_conn_url = format!("{}://:{}@{}", uri_scheme, redis_password, redis_host_name); redis::Client::open(redis_conn_url)
.expect("Invalid connection URL")
.get_connection()
.expect("failed to connect to Redis")
}

Basic operations on Strings and Integers

This function covers SET, GET, and INCR commands. The low-level API is used for SET and GET, which sets and retrieves the value for a key named foo. The INCRBY command is executed using a high-level API i.e. incr increments the value of a key (named counter) by 2 followed by a call to get to retrieve it.

fn basics() {
let mut conn = connect();
let _: () = redis::cmd("SET")
.arg("foo")
.arg("bar")
.query(&mut conn)
.expect("failed to execute SET for 'foo'");
let bar: String = redis::cmd("GET")
.arg("foo")
.query(&mut conn)
.expect("failed to execute GET for 'foo'");
println!("value for 'foo' = {}", bar);
let _: () = conn
.incr("counter", 2)
.expect("failed to execute INCR for 'counter'");
let val: i32 = conn
.get("counter")
.expect("failed to execute GET for 'counter'");
println!("counter = {}", val);
}

How to use a Hash data structure?

The below code snippet demonstrates the functionality of a Redis HASH data structure. HSET is invoked using the low-level API to store information (name, version, repo) about Redis drivers (clients). For example, details for the Rust driver (one being used in this sample code!) is captured in form of a BTreeMap and then passed on to the low-level API. It is then retrieved using HGETALL.

fn hash() {
let mut conn = connect();
let mut driver: BTreeMap<String, String> = BTreeMap::new();
let prefix = "redis-driver";
driver.insert(String::from("name"), String::from("redis-rs"));
driver.insert(String::from("version"), String::from("0.19.0"));
driver.insert(
String::from("repo"),
String::from("https://github.com/mitsuhiko/redis-rs"),
);
let _: () = redis::cmd("HSET")
.arg(format!("{}:{}", prefix, "rust"))
.arg(driver)
.query(&mut conn)
.expect("failed to execute HSET");
let info: BTreeMap<String, String> = redis::cmd("HGETALL")
.arg(format!("{}:{}", prefix, "rust"))
.query(&mut conn)
.expect("failed to execute HGETALL");
println!("info for rust redis driver: {:?}", info);
let _: () = conn
.hset_multiple(
format!("{}:{}", prefix, "go"),
&[
("name", "go-redis"),
("version", "8.4.6"),
("repo", "https://github.com/go-redis/redis"),
],
)
.expect("failed to execute HSET");
let repo_name: String = conn
.hget(format!("{}:{}", prefix, "go"), "repo")
.expect("HGET failed");
println!("go redis driver repo name: {:?}", repo_name);
}

Using Redis Lists

In the function below, you can see how to use a LIST data structure. LPUSH is executed (with the low-level API) to add an entry to the list and the high-level lpop method is used to retrieve that from the list. Then, the rpush method is used to add a couple of entries to the list which are then fetched using the low-level lrange method.

fn list() {
let mut conn = connect();
let list_name = "items";
let _: () = redis::cmd("LPUSH")
.arg(list_name)
.arg("item-1")
.query(&mut conn)
.expect("failed to execute LPUSH for 'items'");
let item: String = conn
.lpop(list_name)
.expect("failed to execute LPOP for 'items'");
println!("first item: {}", item);
let _: () = conn.rpush(list_name, "item-2").expect("RPUSH failed");
let _: () = conn.rpush(list_name, "item-3").expect("RPUSH failed");
let len: isize = conn
.llen(list_name)
.expect("failed to execute LLEN for 'items'");
println!("no. of items in list = {}", len);
let items: Vec<String> = conn
.lrange(list_name, 0, len - 1)
.expect("failed to execute LRANGE for 'items'");
println!("listing items in list");
for item in items {
println!("item: {}", item)
}
}

Store unique items in a Redis Set

Here you can see some of the SET operations. The sadd (high-level API) method is used to add couple of entries to a SET named users. SISMEMBER is then executed (low-level API) to check whether user1 exists. Finally, smembers is used to fetch and iterate over all the set entries in the form of a Vector (Vec).

fn set() {
let mut conn = connect();
let set_name = "users";
let _: () = conn
.sadd(set_name, "user1")
.expect("failed to execute SADD for 'users'");
let _: () = conn
.sadd(set_name, "user2")
.expect("failed to execute SADD for 'users'");
let ismember: bool = redis::cmd("SISMEMBER")
.arg(set_name)
.arg("user1")
.query(&mut conn)
.expect("failed to execute SISMEMBER for 'users'");
println!("does user1 exist in the set? {}", ismember);
let users: Vec<String> = conn.smembers(set_name).expect("failed to execute SMEMBERS");
println!("listing users in set");
for user in users {
println!("user: {}", user)
}
}

Take advantage of Redis Sorted Sets

sorted_set function below demonstrates the Sorted Set data structure. ZADD is invoked (with the low-level API) to add a random integer score for a player (player-1). Next, the zadd method (high-level API) is used to add more players (player-2 to player-5) and their respective (randomly generated) scores. The number of entries in the sorted set is figured out using ZCARD and that's used as the limit to the ZRANGE command (invoked with the low-level API) to list out the players with their scores in ascending order.

fn sorted_set() {
let mut conn = connect();
let sorted_set = "leaderboard";
let _: () = redis::cmd("ZADD")
.arg(sorted_set)
.arg(rand::thread_rng().gen_range(1..10))
.arg("player-1")
.query(&mut conn)
.expect("failed to execute ZADD for 'leaderboard'");
for num in 2..=5 {
let _: () = conn
.zadd(
sorted_set,
String::from("player-") + &num.to_string(),
rand::thread_rng().gen_range(1..10),
)
.expect("failed to execute ZADD for 'leaderboard'");
}
let count: isize = conn
.zcard(sorted_set)
.expect("failed to execute ZCARD for 'leaderboard'");
let leaderboard: Vec<(String, isize)> = conn
.zrange_withscores(sorted_set, 0, count - 1)
.expect("ZRANGE failed");
println!("listing players and scores in ascending order"); for item in leaderboard {
println!("{} = {}", item.0, item.1)
}
}

Run the sample application

Start by cloning the GitHub repo:

git clone https://github.com/abhirockzz/rust-redis-101.git
cd rust-redis-101
export REDIS_HOSTNAME=localhost:6379
export REDIS_HOSTNAME=<Host name>:<port> (e.g. <name of cache>.redis.cache.windows.net:6380)export REDIS_PASSWORD=<Primary Access Key>export IS_TLS=true
cargo run
******* Running SET, GET, INCR commands *******
value for 'foo' = bar
counter = 29
******* Running HASH commands *******
info for rust redis driver: {"name": "redis-rs", "repo": "https://github.com/mitsuhiko/redis-rs", "version": "0.19.0"}
go redis driver repo name: "https://github.com/go-redis/redis"
******* Running LIST commands *******
first item: item-1
no. of items in list = 2
listing items in list
item: item-2
item: item-3
******* Running SET commands *******
does user1 exist in the set? true
listing users in set
user: user2
user: user1
user: user3
******* Running SORTED SET commands *******
listing players and scores
player-4 = 3
player-3 = 7
player-1 = 8
player-2 = 8
player-5 = 8

Conclusion

In this tutorial, you learned how to use the Rust driver for Redis to connect and execute operations in Azure Cache for Redis. If you found this helpful, you may want to explore these additional resources:

--

--

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +768K followers.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abhishek Gupta

Principal Developer Advocate at AWS | I ❤️ Databases, Go, Kubernetes