1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// Panic policy — the achievable analogue of the line above.
//
// No `forbid(panics)` can exist: a panic has no single syntactic form. It
// escapes from `unwrap`/`expect`/`panic!`/`unreachable!` *and* from indexing,
// integer arithmetic and dozens of std methods. What is enforceable is three
// families of lint, and all three are denied:
//
// * the explicit-panic family, crate-wide here;
// * `arithmetic_side_effects`, crate-wide here — every `+ - * / %` on integers
// that the compiler cannot prove safe. It covers the failure mode that has
// produced the most defects in this crate: an announced length or a counter
// that overflows, which panics in debug builds and *wraps silently* in release
// ones, turning a hostile length into a plausible offset;
// * `indexing_slicing` — the only lint covering `a[i]` / `a[i..j]`, the crate's
// largest panic surface by count — in the two zones where a panic is fatal
// rather than merely wrong: `network/` (a panic in the network task kills the
// client with no reconnect) and `resp/` (fed directly by server bytes). See
// their `mod.rs`.
//
// This is deny-plus-justified-allow, not a blanket ban: not every panic is a
// bug. An `unreachable!` on an exhaustive internal match, an index guarded by
// the compare on the line above, and stepping a slice offset past a byte that
// was just read are correct code, and rewriting them into `.get().unwrap()` or
// `checked_add` would trade clarity — and, in the parser, throughput — for
// nothing. Every surviving site therefore carries `#[expect(…, reason = "…")]`
// naming the invariant that makes it unreachable — the same contract a
// `// SAFETY:` comment carries over an `unsafe` block, and reviewable the same
// way. `expect` rather than `allow`, so a justification whose lint stops firing
// becomes a warning and is deleted instead of rotting.
//
// `warn` would have been indistinguishable from `deny` here: CI runs clippy
// with `-D warnings`, so the real tiers are enforced and exempt. Test code is
// exempt (`#![allow]` at the top of each test module): a test that panics is a
// test that failed, which is the mechanism, not a defect.
// `pub` on an item the outside world cannot reach is a lie the compiler does not
// otherwise report: it suppresses `dead_code`, and a reader — or a CHANGELOG
// entry — takes it for public API. This lint covers the method-level half of the
// problem. It does not fire on a type re-exported by a `pub(crate) use` glob, so
// the module boundary in `resp/mod.rs` still has to be read, not trusted.
/*!
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](https://redis.io/commands/) up to and including Redis 8.8
* Async support ([tokio](https://tokio.rs/))
* Different client types:
* Single client
* [Multiplexed](https://redis.com/blog/multiplexing-explained/) client
* Pooled client manager (based on [bb8](https://docs.rs/bb8/latest/bb8/))
* Automatic command batching
* Advanced reconnection & retry strategy
* [Pipelining](https://redis.io/docs/manual/pipelining/) support
* Configuration with Redis URL or dedicated builder
* [TLS](https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/) support
* [Transaction](https://redis.io/docs/manual/transactions/) support
* [Pub/sub](https://redis.io/docs/manual/pubsub/) support
* [Sentinel](https://redis.io/docs/manual/sentinel/) support
* [LUA Scripts/Functions](https://redis.io/docs/manual/programmability/) support
* [Cluster](https://redis.io/docs/manual/scaling/) support
* [Client-side caching](https://redis.io/docs/latest/develop/reference/client-side-caching/) support
# Optional Features
| Feature | Description |
| ------- | ----------- |
| `tokio-runtime` | [Tokio](https://tokio.rs/) 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.
The remaining features are for developing rustis itself and carry **no stability
guarantee**: `bench` (exposes internal RESP entry points to the benchmarks and pulls in
`criterion`, `fred`, `redis` and `pprof`), `fuzzing` (same, for the `cargo-fuzz` targets
in `fuzz/`) and `web-examples` (`axum` / `actix-web`, for the examples).
# 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:
```bash
redis-cli --raw HELLO 3
```
If 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(())
}
```
# Client
See the module [`client`] to discover which are the 3
usages of the [`Client`](client::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](https://redis.io/docs/reference/protocol-spec/).
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`](resp::Value), which is a generic Rust data structure over RESP.
* Convert Rust types into Rust Commands with the [`Command`](resp::Command) struct, whose
arguments are any type implementing serde's [`Serialize`](serde::Serialize).
* Convert Rust command responses into Rust type with serde and helpful marker traits.
# Commands
In order to send [Commands](https://redis.io/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](https://redis.io/docs/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](https://redis.io/resources/modules/)
not included in [Redis Stack](https://redis.io/docs/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`](crate::resp::cmd) function to specify the name of the command,
followed by one or multiple calls to [`CommandBuilder::arg`](crate::resp::CommandBuilder::arg) to add arguments to the command,
and to [`CommandBuilder::key`](crate::resp::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:
* [`send`](crate::client::Client::send)
* [`send_and_forget`](crate::client::Client::send_and_forget)
* [`Pipeline::queue`](crate::client::Pipeline::queue), 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`](crate::resp::CommandBuilder::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.
# Client-side caching
See the module [`cache`] to discover how you can implement client-side caching.
*/
pub use bb8;
pub use *;
use *;
/// Library general result type.
pub type Result<T> = Result;
/// Library general future type.
pub type Future<'a, T> = BoxFuture;
// Every function of `network::async_executor_strategy` is provided by a runtime
// feature and has no fallback body. Without this guard the user gets a dozen
// "not found in this scope" errors instead of the actual cause.
compile_error!;
compile_error!;
// The backend-only features gate the TLS configuration types; the connection
// code that reads them lives behind the runtime feature. Enabled alone they
// build a `TlsConfig` nothing would ever use, so name that rather than let the
// missing stream types surface as "not found in this scope".
compile_error!;
compile_error!;