Skip to content
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

Add the join! macro #406

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
147 changes: 147 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,150 @@ pub use rayon_core::ThreadPool;
pub use rayon_core::join;
pub use rayon_core::{scope, Scope};
pub use rayon_core::spawn;

/// Fork and join many expressions at once.
///
/// The syntax is one or more occurrences of
///
/// ```ignore
/// let <irrefutable pattern> = fork <closure expresssion>;`.
/// ```
///
/// For example,
///
/// ```
/// #[macro_use]
/// extern crate rayon;
///
/// # fn main() {
/// join! {
/// let w = fork || 0;
/// let x = fork || 1;
/// let y = fork || 2;
/// let z = fork || 3;
/// }
///
/// assert_eq!(w, 0);
/// assert_eq!(x, 1);
/// assert_eq!(y, 2);
/// assert_eq!(z, 3);
/// # }
/// ```
///
/// This is equivalent to nesting calls to `rayon::join` like this:
///
/// ```
/// # extern crate rayon;
/// let (w, (x, (y, z))) = rayon::join(
/// || 0,
/// || rayon::join(
/// || 1,
/// || rayon::join(
/// || 2,
/// || 3,
/// )
/// )
/// );
/// ```
///
/// Alternatively, you can just get a flattened tuple of results, without
/// binding the results to any variable inside the macro.
///
/// The syntax is one or more occurrences of `<closure expression> ,` where the
/// last `,` is optional.
///
/// ```rust
/// #[macro_use]
/// extern crate rayon;
///
/// # fn main() {
/// let (w, x, y, z) = join!(|| 0, || 1, || 2, || 3);
///
/// assert_eq!(w, 0);
/// assert_eq!(x, 1);
/// assert_eq!(y, 2);
/// assert_eq!(z, 3);
/// # }
/// ```
#[macro_export]
macro_rules! join {
// Entry point for `let <pat> = fork <closure>;` usage.
( $( let $lhs:pat = fork $rhs:expr ; )+ ) => {
let join!( @left $( $lhs , )+ ) = join!( @right $( $rhs , )+ );
};

// Entry point for `<closure>,` usage.
( $x:expr $( , $xs:expr )* ) => {
join! { @flat $x $( , $xs )* }
};

// Flattening tuples with temporary variables.
( @flat $( let $lhs:ident = $rhs:expr ; )+ ) => {
{
let join!( @left $( $lhs , )+ ) = join!( @right $( $rhs , )+ );
($( $lhs ),+)
}
};
( @flat $( let $lhs:ident = $rhs:expr ; )* $x:expr $( , $xs:expr )*) => {
join! { @flat
$( let $lhs = $rhs ; )*
let lhs = $x;
$($xs),*
}
};

// Left hand side recursion to nest individual patterns into tuple patterns
// like `(x, (y, (z, ...)))`.
( @left $x:pat , ) => {
$x
};
( @left $x:pat , $( $xs:pat , )+ ) => {
( $x , join!( @left $( $xs , )+ ) )
};

// Right hand side recursion to nest exprs into rayon fork-joins
// like:
//
// rayon::join(
// x,
// || rayon::join(
// y,
// || rayon::join(
// z,
// || ...)))
( @right $x:expr , ) => {
($x)()
};
( @right $x:expr , $( $xs:expr , )+ ) => {
::rayon::join( $x , || join!( @right $( $xs , )+ ) )
}
}

// Necessary for the tests using macros that expand to `::rayon::whatever`.
#[cfg(test)]
mod rayon {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use $crate in the macro and remove this helper module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point -- the macro should use $crate anyway.

pub use super::*;
}

#[cfg(test)]
mod tests {
#[macro_use]
use super::*;

#[test]
fn join_macro_with_more_complex_patterns() {
struct Point(usize, usize);

join! {
let Point(w, x) = fork || Point(1, 2);
let Point(y, z) = fork || Point(3, 4);
let (((((a, _), _), _), _), _) = fork || (((((5, 4), 3), 2), 1), 0);
};

assert_eq!(w, 1);
assert_eq!(x, 2);
assert_eq!(y, 3);
assert_eq!(z, 4);
assert_eq!(a, 5);
}
}