Skip to content

Remove unsafe iterators. #1660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust

on:
push:
branches: [ main, 0.*.x ]
branches: [ main, 0.*.x, 1.*.x ]
pull_request:
branches: [ main, 0.*.x ]
branches: [ main, 0.*.x, 1.*.x ]

env:
CARGO_TERM_COLOR: always
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 4 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
build:
@RUSTFLAGS="-D warnings" cargo build -F safe_iterators --locked -p redis
@RUSTFLAGS="-D warnings" cargo build --locked -p redis

test:
@echo "===================================================================="
Expand All @@ -8,14 +8,9 @@ test:
@RUSTFLAGS="-D warnings" cargo build --locked -p redis -p redis-test --all-features

@echo "===================================================================="
@echo "Testing Connection Type TCP without features except safe_iterators"
@echo "Testing Connection Type TCP without features"
@echo "===================================================================="
@RUSTFLAGS="-D warnings" REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo nextest run --locked -p redis --no-default-features -F safe_iterators -E 'not test(test_module)'

@echo "===================================================================="
@echo "Testing Connection Type TCP without safe_iterators, but with async"
@echo "===================================================================="
@RUSTFLAGS="-D warnings -A deprecated" REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo nextest run --locked -p redis --no-default-features -F tokio-comp -E 'not test(test_module)'
@RUSTFLAGS="-D warnings" REDISRS_SERVER_TYPE=tcp RUST_BACKTRACE=1 cargo nextest run --locked -p redis --no-default-features -E 'not test(test_module)'

@echo "===================================================================="
@echo "Testing Connection Type TCP with all features and RESP2"
Expand All @@ -35,7 +30,7 @@ test:
@echo "===================================================================="
@echo "Testing Connection Type TCP with native-TLS support"
@echo "===================================================================="
@RUSTFLAGS="-D warnings" REDISRS_SERVER_TYPE=tcp+tls RUST_BACKTRACE=1 cargo nextest run --locked -p redis --features=json,tokio-native-tls-comp,async-std-native-tls-comp,smol-native-tls-comp,connection-manager,cluster-async,safe_iterators -E 'not test(test_module)'
@RUSTFLAGS="-D warnings" REDISRS_SERVER_TYPE=tcp+tls RUST_BACKTRACE=1 cargo nextest run --locked -p redis --features=json,tokio-native-tls-comp,async-std-native-tls-comp,smol-native-tls-comp,connection-manager,cluster-async -E 'not test(test_module)'

@echo "===================================================================="
@echo "Testing Connection Type UNIX"
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The crate is called `redis` and you can depend on it via cargo:

```ini
[dependencies]
redis = "0.32.0"
redis = "1.0"
```

Documentation on the library can be found at
Expand Down Expand Up @@ -75,13 +75,13 @@ To enable asynchronous clients, enable the relevant feature in your Cargo.toml,

```
# if you use tokio
redis = { version = "0.32.0", features = ["tokio-comp"] }
redis = { version = "1.0", features = ["tokio-comp"] }

# if you use smol
redis = { version = "0.32.0", features = ["smol-comp"] }
redis = { version = "1.0", features = ["smol-comp"] }

# if you use async-std
redis = { version = "0.32.0", features = ["async-std-comp"] }
redis = { version = "1.0", features = ["async-std-comp"] }
```

You can then use either the `AsyncCommands` or `AsyncTypedCommands` trait.
Expand All @@ -92,7 +92,7 @@ When using a sync connection, it is recommended to use a connection pool in orde
disconnects or multi-threaded usage. This can be done using the `r2d2` feature.

```
redis = { version = "0.32.0", features = ["r2d2"] }
redis = { version = "1.0", features = ["r2d2"] }
```

For async connections, connection pooling isn't necessary, unless blocking commands are used.
Expand All @@ -114,31 +114,31 @@ Currently, `native-tls` and `rustls` are supported.
To use `native-tls`:

```
redis = { version = "0.32.0", features = ["tls-native-tls"] }
redis = { version = "1.0", features = ["tls-native-tls"] }

# if you use tokio
redis = { version = "0.32.0", features = ["tokio-native-tls-comp"] }
redis = { version = "1.0", features = ["tokio-native-tls-comp"] }

# if you use smol
redis = { version = "0.32.0", features = ["smol-native-tls-comp"] }
redis = { version = "1.0", features = ["smol-native-tls-comp"] }

# if you use async-std
redis = { version = "0.32.0", features = ["async-std-native-tls-comp"] }
redis = { version = "1.0", features = ["async-std-native-tls-comp"] }
```

To use `rustls`:

```
redis = { version = "0.32.0", features = ["tls-rustls"] }
redis = { version = "1.0", features = ["tls-rustls"] }

# if you use tokio
redis = { version = "0.32.0", features = ["tokio-rustls-comp"] }
redis = { version = "1.0", features = ["tokio-rustls-comp"] }

# if you use smol
redis = { version = "0.32.0", features = ["smol-rustls-comp"] }
redis = { version = "1.0", features = ["smol-rustls-comp"] }

# if you use async-std
redis = { version = "0.32.0", features = ["async-std-rustls-comp"] }
redis = { version = "1.0", features = ["async-std-rustls-comp"] }
```

Add `rustls` to dependencies
Expand Down Expand Up @@ -179,7 +179,7 @@ let client = redis::Client::open("rediss://127.0.0.1/#insecure")?;

Support for Redis Cluster can be enabled by enabling the `cluster` feature in your Cargo.toml:

`redis = { version = "0.32.0", features = [ "cluster"] }`
`redis = { version = "1.0", features = [ "cluster"] }`

Then you can simply use the `ClusterClient`, which accepts a list of available nodes. Note
that only one node in the cluster needs to be specified when instantiating the client, though
Expand All @@ -202,7 +202,7 @@ fn fetch_an_integer() -> String {
Async Redis Cluster support can be enabled by enabling the `cluster-async` feature, along
with your preferred async runtime, e.g.:

`redis = { version = "0.32.0", features = [ "cluster-async", "tokio-std-comp" ] }`
`redis = { version = "1.0", features = [ "cluster-async", "tokio-std-comp" ] }`

```rust
use redis::cluster::ClusterClient;
Expand All @@ -222,7 +222,7 @@ async fn fetch_an_integer() -> String {

Support for the RedisJSON Module can be enabled by specifying "json" as a feature in your Cargo.toml.

`redis = { version = "0.32.0", features = ["json"] }`
`redis = { version = "1.0", features = ["json"] }`

Then you can simply import the `JsonCommands` trait which will add the `json` commands to all Redis Connections (not to be confused with just `Commands` which only adds the default commands)

Expand Down
4 changes: 2 additions & 2 deletions config/global.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
max_combo_size=4
skip_optional_deps=false
max_combo_size = 3
skip_optional_deps = false
5 changes: 0 additions & 5 deletions config/redis.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,3 @@ forbid = [
"OR",
"num-bigint",
]

# Since the omission of the feature `safe_iterators` will produce deprecation errors, it is always required.
[[rule]]
when = true
require = ["safe_iterators"]
5 changes: 2 additions & 3 deletions redis-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ rust-version = "1.75"
bench = false

[dependencies]
redis = { version = "0.32", path = "../redis" }
redis = { version = "1.0.0-alpha", path = "../redis" }
bytes = { version = "1", optional = true }
futures = { version = "0.3", optional = true }
tempfile = "=3.20.0"
Expand All @@ -24,10 +24,9 @@ rand = "0.9"
aio = ["futures", "redis/aio"]

[dev-dependencies]
redis = { version = "0.32", path = "../redis", features = [
redis = { version = "1.0.0-alpha", path = "../redis", features = [
"aio",
"tokio-comp",
"safe_iterators",
] }
tokio = { version = "1", features = [
"rt",
Expand Down
3 changes: 1 addition & 2 deletions redis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "redis"
version = "0.32.0"
version = "1.0.0-alpha"
keywords = ["redis", "valkey", "cluster", "sentinel", "pubsub"]
description = "Redis driver for Rust."
homepage = "https://github.com/redis-rs/redis-rs"
Expand Down Expand Up @@ -154,7 +154,6 @@ disable-client-setinfo = []
cache-aio = ["aio", "dep:lru"]
r2d2 = ["dep:r2d2"]
bb8 = ["dep:bb8"]
safe_iterators = []

# Deprecated features
tls = ["tls-native-tls"] # use "tls-native-tls" instead
Expand Down
3 changes: 0 additions & 3 deletions redis/examples/async-scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ async fn main() -> redis::RedisResult<()> {
let _: () = con.set("async-key2", b"foo").await?;

let iter: AsyncIter<String> = con.scan().await?;
#[cfg(not(feature = "safe_iterators"))]
let mut keys: Vec<String> = iter.collect().await;
#[cfg(feature = "safe_iterators")]
let mut keys: Vec<_> = iter.map(std::result::Result::unwrap).collect().await;

keys.sort();
Expand Down
17 changes: 4 additions & 13 deletions redis/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,10 @@ fn do_show_scanning(con: &mut redis::Connection) -> redis::RedisResult<()> {
let iter = cmd.iter::<i32>(con)?;

// as a simple exercise we just sum up the iterator.
let sum: i32 = {
#[cfg(feature = "safe_iterators")]
{
let mut sum = 0;
for result in iter {
sum += result?;
}
sum
}

#[cfg(not(feature = "safe_iterators"))]
iter.sum()
};
let mut sum = 0;
for result in iter {
sum += result?;
}

println!("The sum of all numbers in the set 0-1000: {sum}");

Expand Down
55 changes: 1 addition & 54 deletions redis/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,30 +86,10 @@ pub struct Cmd {
cache: Option<CommandCacheConfig>,
}

#[cfg_attr(
not(feature = "safe_iterators"),
deprecated(
note = "Deprecated due to the fact that this implementation silently stops at the first value that can't be converted to T. Enable the feature `safe_iterators` for a safe version."
)
)]
/// Represents a redis iterator.
pub struct Iter<'a, T: FromRedisValue> {
iter: CheckedIter<'a, T>,
}

#[cfg(not(feature = "safe_iterators"))]
impl<T: FromRedisValue> Iterator for Iter<'_, T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<T> {
// use the checked iterator, but keep the behavior of the deprecated
// iterator. This will return silently `None` if an error occurs.
self.iter.next()?.ok()
}
}

#[cfg(feature = "safe_iterators")]
impl<T: FromRedisValue> Iterator for Iter<'_, T> {
type Item = RedisResult<T>;

Expand Down Expand Up @@ -180,12 +160,6 @@ enum IterOrFuture<'a, T: FromRedisValue + 'a> {

/// Represents a redis iterator that can be used with async connections.
#[cfg(feature = "aio")]
#[cfg_attr(
all(feature = "aio", not(feature = "safe_iterators")),
deprecated(
note = "Deprecated due to the fact that this implementation silently stops at the first value that can't be converted to T. Enable the feature `safe_iterators` for a safe version."
)
)]
pub struct AsyncIter<'a, T: FromRedisValue + 'a> {
inner: IterOrFuture<'a, T>,
}
Expand Down Expand Up @@ -239,39 +213,15 @@ impl<'a, T: FromRedisValue + 'a + Unpin + Send> AsyncIter<'a, T> {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "safe_iterators")]
#[inline]
pub async fn next_item(&mut self) -> Option<RedisResult<T>> {
StreamExt::next(self).await
}

/// ```rust,no_run
/// # use redis::AsyncCommands;
/// # async fn scan_set() -> redis::RedisResult<()> {
/// # let client = redis::Client::open("redis://127.0.0.1/")?;
/// # let mut con = client.get_multiplexed_async_connection().await?;
/// let _: () = con.sadd("my_set", 42i32).await?;
/// let _: () = con.sadd("my_set", 43i32).await?;
/// let mut iter: redis::AsyncIter<i32> = con.sscan("my_set").await?;
/// while let Some(element) = iter.next_item().await {
/// assert!(element == 42 || element == 43);
/// }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "safe_iterators"))]
#[inline]
pub async fn next_item(&mut self) -> Option<T> {
StreamExt::next(self).await
}
}

#[cfg(feature = "aio")]
impl<'a, T: FromRedisValue + Unpin + Send + 'a> Stream for AsyncIter<'a, T> {
#[cfg(feature = "safe_iterators")]
type Item = RedisResult<T>;
#[cfg(not(feature = "safe_iterators"))]
type Item = T;

fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
Expand All @@ -293,10 +243,7 @@ impl<'a, T: FromRedisValue + Unpin + Send + 'a> Stream for AsyncIter<'a, T> {
Poll::Ready((iter, value)) => {
this.inner = IterOrFuture::Iter(iter);

#[cfg(feature = "safe_iterators")]
return Poll::Ready(value);
#[cfg(not(feature = "safe_iterators"))]
Poll::Ready(value.map(|res| res.ok()).flatten())
Poll::Ready(value)
}
},
IterOrFuture::Empty => unreachable!(),
Expand Down
5 changes: 5 additions & 0 deletions redis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,11 @@ let primary = sentinel.get_async_connection().await.unwrap();
# Ok(()) }
"##
)]
//!```
//!
//! # Upgrading to version 1
//!
//! * Iterators are now safe by default, without an opt-out. This means that the iterators return `RedisResult<Value>` instead of `Value` (see [this PR](https://github.com/redis-rs/redis-rs/pull/1641) for background). If you previously used the "safe_iterators" feature to opt-in to this behavior, just remove the feature declaration. Otherwise you will need to adjust your usage of iterators to account for potential conversion failures.
//!

#![deny(non_camel_case_types)]
Expand Down
7 changes: 0 additions & 7 deletions redis/tests/test_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,6 @@ mod basic_async {
.unwrap();

while let Some(x) = iter.next_item().await {
#[cfg(feature = "safe_iterators")]
let x = x?;

// if this assertion fails, too many items were returned by the iterator.
Expand Down Expand Up @@ -634,7 +633,6 @@ mod basic_async {
.unwrap();

while let Some(item) = iter.next_item().await {
#[cfg(feature = "safe_iterators")]
let item = item?;

// if this assertion fails, too many items were returned by the iterator.
Expand Down Expand Up @@ -682,7 +680,6 @@ mod basic_async {
.unwrap();

while let Some(item) = iter.next_item().await {
#[cfg(feature = "safe_iterators")]
let item = item?;

// if this assertion fails, too many items were returned by the iterator.
Expand Down Expand Up @@ -939,9 +936,6 @@ mod basic_async {
}

let iter: redis::AsyncIter<String> = con.scan().await.unwrap();
#[cfg(not(feature = "safe_iterators"))]
let mut keys_from_redis: Vec<String> = iter.collect().await;
#[cfg(feature = "safe_iterators")]
let mut keys_from_redis: Vec<_> =
iter.map(std::result::Result::unwrap).collect().await;
keys_from_redis.sort();
Expand Down Expand Up @@ -1021,7 +1015,6 @@ mod basic_async {
.unwrap();
}

#[cfg(feature = "safe_iterators")]
#[rstest]
// Test issue of AsyncCommands::scan not returning keys because wrong assumptions about the key type were made
// https://github.com/redis-rs/redis-rs/issues/1309
Expand Down
Loading