Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
and [`IntoPyObject`](https://docs.rs/pyo3/0.29/pyo3/conversion/trait.IntoPyObject.html) for `CompactString`, so it can
be used seamlessly with [`pyo3`](https://crates.io/crates/pyo3) v0.29.
* Implemented in [`feat: add pyo3 feature`](https://github.com/ParkMyCar/compact_str/pull/446).
* Added a `rayon` feature which implements [`FromParallelIterator`](https://docs.rs/rayon/1/rayon/iter/trait.FromParallelIterator.html)
and [`ParallelExtend`](https://docs.rs/rayon/1/rayon/iter/trait.ParallelExtend.html) for `CompactString`
(mirroring [`String`], plus the `Item = CompactString` variants), so it can be collected from and
extended by [`rayon`](https://crates.io/crates/rayon) parallel iterators.
* Implemented in [`feat: add rayon feature`](https://github.com/ParkMyCar/compact_str/pull/487).
* Bump the [`sqlx`](https://crates.io/crates/sqlx) dependency to `v0.9`. Enabling the `sqlx`
feature now requires Rust `v1.94`.
* Implemented in [`deps: bump sqlx to 0.9`](https://github.com/ParkMyCar/compact_str/pull/474).
Expand Down
2 changes: 2 additions & 0 deletions compact_str/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ markup = ["dep:markup"]
proptest = ["dep:proptest"]
pyo3 = ["dep:pyo3"]
quickcheck = ["dep:quickcheck"]
rayon = ["dep:rayon", "std"]
rkyv = ["dep:rkyv"]
schemars = ["dep:schemars"]
serde = ["dep:serde"]
Expand All @@ -53,6 +54,7 @@ proptest = { version = "1", optional = true, default-features = false, features
] }
pyo3 = { version = "0.29", optional = true, default-features = false }
quickcheck = { version = "1", optional = true, default-features = false }
rayon = { version = "1", optional = true }
rkyv = { version = "0.8", optional = true, default-features = false }
schemars = { version = "1", optional = true, default-features = false }
serde = { version = "1", optional = true, default-features = false, features = [
Expand Down
1 change: 1 addition & 0 deletions compact_str/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ This crate exposes one macro `format_compact!` that can be used to create `Compa
* `smallvec`, provides the `into_bytes()` method which enables you to convert a `CompactString` into a byte vector, using [`smallvec::SmallVec`](https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html)
* `valuable`, which implements the [`valuable::Valuable`](https://docs.rs/valuable/0.1.1/valuable/trait.Valuable.html) trait for object-safe value inspection
* `pyo3`, which implements [`FromPyObject`](https://docs.rs/pyo3/0.29/pyo3/conversion/trait.FromPyObject.html) and [`IntoPyObject`](https://docs.rs/pyo3/0.29/pyo3/conversion/trait.IntoPyObject.html), so `CompactString`s can be converted to and from Python strings in [`pyo3`](https://github.com/PyO3/pyo3) extensions
* `rayon`, which implements [`FromParallelIterator`](https://docs.rs/rayon/1/rayon/iter/trait.FromParallelIterator.html) and [`ParallelExtend`](https://docs.rs/rayon/1/rayon/iter/trait.ParallelExtend.html), so `CompactString`s can be collected from and extended by [`rayon`](https://github.com/rayon-rs/rayon) parallel iterators

### How it works
Note: this explanation assumes a 64-bit architecture, for 32-bit architectures generally divide any number by 2.
Expand Down
2 changes: 2 additions & 0 deletions compact_str/src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ mod proptest;
mod pyo3;
#[cfg(feature = "quickcheck")]
mod quickcheck;
#[cfg(feature = "rayon")]
mod rayon;
#[cfg(feature = "rkyv")]
mod rkyv;
#[cfg(feature = "schemars")]
Expand Down
173 changes: 173 additions & 0 deletions compact_str/src/features/rayon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Implements [`rayon`]'s [`FromParallelIterator`] and [`ParallelExtend`] traits for
//! [`CompactString`], so it can be built from (or extended by) a parallel iterator the same way
//! [`String`] can -- including the `Item = CompactString` variants.
//!
//! Every impl delegates the actual parallel collection to `String` (reusing rayon's optimized
//! string collectors) and then converts, so the behavior matches `String` exactly.

use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;

use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator};

use crate::CompactString;

/// Mirrors both `rayon` string-collection traits for `CompactString`, for each item type that
/// rayon can already collect into a `String`.
macro_rules! impl_via_string {
($item:ty $(, $lt:lifetime)?) => {
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl$(<$lt>)? FromParallelIterator<$item> for CompactString {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = $item>,
{
CompactString::from(String::from_par_iter(par_iter))
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl$(<$lt>)? ParallelExtend<$item> for CompactString {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = $item>,
{
let mut buffer = String::new();
buffer.par_extend(par_iter);
self.push_str(&buffer);
}
}
};
}

impl_via_string!(char);
impl_via_string!(&'a char, 'a);
impl_via_string!(&'a str, 'a);
impl_via_string!(String);
impl_via_string!(Box<str>);
impl_via_string!(Cow<'a, str>, 'a);

// `Item = CompactString`: rayon has no collectors for our type, so map each `CompactString` into a
// `String` first and reuse the `String` collectors.
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl FromParallelIterator<CompactString> for CompactString {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = CompactString>,
{
CompactString::from(String::from_par_iter(
par_iter.into_par_iter().map(CompactString::into_string),
))
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl ParallelExtend<CompactString> for CompactString {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = CompactString>,
{
let mut buffer = String::new();
buffer.par_extend(par_iter.into_par_iter().map(CompactString::into_string));
self.push_str(&buffer);
}
}

// The reverse direction: collect/extend a `String` from `CompactString`s. Permitted by the orphan
// rule because `CompactString` is a local type.
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl FromParallelIterator<CompactString> for String {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = CompactString>,
{
String::from_par_iter(par_iter.into_par_iter().map(CompactString::into_string))
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
impl ParallelExtend<CompactString> for String {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = CompactString>,
{
self.par_extend(par_iter.into_par_iter().map(CompactString::into_string));
}
}

#[cfg(test)]
mod tests {
use alloc::string::String;
use alloc::vec::Vec;

use rayon::prelude::*;
use test_strategy::proptest;

use crate::tests::rand_unicode;
use crate::CompactString;

#[test]
#[cfg_attr(miri, ignore)]
fn from_par_iter_chars() {
let compact: CompactString = vec!['h', 'e', 'l', 'l', 'o'].into_par_iter().collect();
assert_eq!(compact, "hello");
}

#[test]
#[cfg_attr(miri, ignore)]
fn from_par_iter_str_slices() {
let compact: CompactString = vec!["com", "pact", "_str"].par_iter().copied().collect();
assert_eq!(compact, "compact_str");
}

#[test]
#[cfg_attr(miri, ignore)]
fn from_par_iter_strings() {
let words = vec![String::from("a"), String::from("bc")];
let compact: CompactString = words.into_par_iter().collect();
assert_eq!(compact, "abc");
}

#[test]
#[cfg_attr(miri, ignore)]
fn par_extend_appends_in_place() {
let mut compact = CompactString::from("start:");
compact.par_extend(vec!['a', 'b', 'c'].into_par_iter());
assert_eq!(compact, "start:abc");
}

#[test]
#[cfg_attr(miri, ignore)]
fn compact_string_items_collect_into_both() {
let words = vec![CompactString::from("foo"), CompactString::from("bar")];
let compact: CompactString = words.clone().into_par_iter().collect();
let string: String = words.into_par_iter().collect();
assert_eq!(compact, "foobar");
assert_eq!(string, "foobar");
}

// Parity + ordering vs `String` across many words. Ignored under Miri, where rayon's thread
// pool is far too slow.
#[proptest]
#[cfg_attr(miri, ignore)]
fn proptest_from_par_iter_matches_string(
#[strategy(proptest::collection::vec(rand_unicode(), 0..20))] words: Vec<String>,
) {
let compact: CompactString = words.par_iter().map(String::as_str).collect();
let control: String = words.par_iter().map(String::as_str).collect();
assert_eq!(compact, control);
}

#[proptest]
#[cfg_attr(miri, ignore)]
fn proptest_par_extend_matches_string(
#[strategy(proptest::collection::vec(rand_unicode(), 0..20))] words: Vec<String>,
) {
let mut compact = CompactString::from("prefix:");
let mut control = String::from("prefix:");
compact.par_extend(words.clone().into_par_iter());
control.par_extend(words.into_par_iter());
assert_eq!(compact, control);
}
}
Loading