use std::future;
use futures_core::Stream;
use futures_util::{StreamExt, stream::select};
pub trait CombineLatest {
type Item;
fn combine_latest(self) -> impl Stream<Item = Self::Item>;
}
pub trait CombineLatestOpt {
type Item;
fn combine_latest_opt(self) -> impl Stream<Item = Self::Item>;
}
pub trait MapLatest<F> {
type Output;
fn map_latest(self, f: F) -> impl Stream<Item = Self::Output>;
}
pub trait MapLatestOpt<F> {
type Output;
fn map_latest_opt(self, f: F) -> impl Stream<Item = Self::Output>;
}
pub trait WithLatestFrom {
type Item;
fn with_latest_from(self) -> impl Stream<Item = Self::Item>;
}
pub trait MapWithLatestFrom<F> {
type Output;
fn map_with_latest_from(self, f: F) -> impl Stream<Item = Self::Output>;
}
macro_rules! nest_select {
($a:expr, $b:expr) => { select($a, $b) };
($a:expr, $b:expr, $($rest:expr),+) => {
nest_select!(select($a, $b), $($rest),+)
};
}
macro_rules! impl_combine_latest {
(
$Tag:ident { $($Var:ident),+ }
[$(($T:ident, $S:ident, $s:ident, $c:ident, $lt:lifetime)),+]
) => {
enum $Tag<$($T),+> { $($Var($T)),+ }
impl<$($T: Clone, $S: Stream<Item = $T>),+> CombineLatest for ($($S,)+) {
type Item = ($($T,)+);
fn combine_latest(self) -> impl Stream<Item = Self::Item> {
let ($($s,)+) = self;
$(let mut $c: Option<$T> = None;)+
nest_select!($($s.map($Tag::$Var)),+)
.filter_map(move |tagged| {
match tagged { $($Tag::$Var(v) => $c = Some(v),)+ }
future::ready(match ($($c.clone(),)+) {
($(Some($s),)+) => Some(($($s,)+)),
_ => None,
})
})
}
}
impl<$($T: Clone, $S: Stream<Item = $T>),+> CombineLatestOpt for ($($S,)+) {
type Item = ($(Option<$T>,)+);
fn combine_latest_opt(self) -> impl Stream<Item = Self::Item> {
let ($($s,)+) = self;
$(let mut $c: Option<$T> = None;)+
nest_select!($($s.map($Tag::$Var)),+)
.map(move |tagged| {
match tagged { $($Tag::$Var(v) => $c = Some(v),)+ }
($($c.clone(),)+)
})
}
}
impl<$($T, $S: Stream<Item = $T>,)+ U, F> MapLatest<F> for ($($S,)+)
where
F: for<$($lt),+> FnMut($(&$lt $T),+) -> U,
{
type Output = U;
fn map_latest(self, mut f: F) -> impl Stream<Item = U> {
let ($($s,)+) = self;
$(let mut $c: Option<$T> = None;)+
nest_select!($($s.map($Tag::$Var)),+)
.filter_map(move |tagged| {
match tagged { $($Tag::$Var(v) => $c = Some(v),)+ }
future::ready(match ($($c.as_ref(),)+) {
($(Some($s),)+) => Some(f($($s),+)),
_ => None,
})
})
}
}
impl<$($T, $S: Stream<Item = $T>,)+ U, F> MapLatestOpt<F> for ($($S,)+)
where
F: for<$($lt),+> FnMut($(Option<&$lt $T>),+) -> U,
{
type Output = U;
fn map_latest_opt(self, mut f: F) -> impl Stream<Item = U> {
let ($($s,)+) = self;
$(let mut $c: Option<$T> = None;)+
nest_select!($($s.map($Tag::$Var)),+)
.map(move |tagged| {
match tagged { $($Tag::$Var(v) => $c = Some(v),)+ }
f($($c.as_ref(),)+)
})
}
}
};
}
macro_rules! impl_with_latest_from {
(
$Tag:ident { $PrimaryVar:ident $(, $SecondaryVar:ident)+ }
[($PT:ident, $PS:ident, $ps:ident, $pc:ident, $plt:lifetime)
$(, ($T:ident, $S:ident, $s:ident, $c:ident, $lt:lifetime))+]
) => {
impl<$PT: Clone, $($T: Clone,)+ $PS: Stream<Item = $PT>, $($S: Stream<Item = $T>),+>
WithLatestFrom for ($PS, $($S,)+)
{
type Item = ($PT, $($T,)+);
fn with_latest_from(self) -> impl Stream<Item = Self::Item> {
let ($ps, $($s,)+) = self;
let mut $pc: Option<$PT> = None;
$(let mut $c: Option<$T> = None;)+
nest_select!($ps.map($Tag::$PrimaryVar), $($s.map($Tag::$SecondaryVar)),+)
.filter_map(move |tagged| {
let is_primary = matches!(tagged, $Tag::$PrimaryVar(_));
match tagged {
$Tag::$PrimaryVar(v) => $pc = Some(v),
$($Tag::$SecondaryVar(v) => $c = Some(v),)+
}
future::ready(if is_primary {
match ($pc.clone(), $($c.clone(),)+) {
(Some($ps), $(Some($s),)+) => Some(($ps, $($s,)+)),
_ => None,
}
} else {
None
})
})
}
}
impl<$PT, $($T,)+ $PS: Stream<Item = $PT>, $($S: Stream<Item = $T>,)+ U, F>
MapWithLatestFrom<F> for ($PS, $($S,)+)
where
F: for<$plt, $($lt),+> FnMut(&$plt $PT, $(&$lt $T),+) -> U,
{
type Output = U;
fn map_with_latest_from(self, mut f: F) -> impl Stream<Item = U> {
let ($ps, $($s,)+) = self;
let mut $pc: Option<$PT> = None;
$(let mut $c: Option<$T> = None;)+
nest_select!($ps.map($Tag::$PrimaryVar), $($s.map($Tag::$SecondaryVar)),+)
.filter_map(move |tagged| {
let is_primary = matches!(tagged, $Tag::$PrimaryVar(_));
match tagged {
$Tag::$PrimaryVar(v) => $pc = Some(v),
$($Tag::$SecondaryVar(v) => $c = Some(v),)+
}
future::ready(if is_primary {
match ($pc.as_ref(), $($c.as_ref(),)+) {
(Some($ps), $(Some($s),)+) => Some(f($ps, $($s),+)),
_ => None,
}
} else {
None
})
})
}
}
};
}
impl_combine_latest!(LR { L, R }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b)]
);
impl_combine_latest!(Tag3 { A, B, C }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c)]
);
impl_combine_latest!(Tag4 { A, B, C, D }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd)]
);
impl_combine_latest!(Tag5 { A, B, C, D, E }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e)]
);
impl_combine_latest!(Tag6 { A, B, C, D, E, F }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f)]
);
impl_combine_latest!(Tag7 { A, B, C, D, E, F, G }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g)]
);
impl_combine_latest!(Tag8 { A, B, C, D, E, F, G, H }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h)]
);
impl_combine_latest!(Tag9 { A, B, C, D, E, F, G, H, I }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i)]
);
impl_combine_latest!(Tag10 { A, B, C, D, E, F, G, H, I, J }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j)]
);
impl_combine_latest!(Tag11 { A, B, C, D, E, F, G, H, I, J, K }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j), (T11, S11, s11, c11, 'k)]
);
impl_combine_latest!(Tag12 { A, B, C, D, E, F, G, H, I, J, K, L }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j), (T11, S11, s11, c11, 'k),
(T12, S12, s12, c12, 'l)]
);
impl_with_latest_from!(LR { L, R }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b)]
);
impl_with_latest_from!(Tag3 { A, B, C }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c)]
);
impl_with_latest_from!(Tag4 { A, B, C, D }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd)]
);
impl_with_latest_from!(Tag5 { A, B, C, D, E }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e)]
);
impl_with_latest_from!(Tag6 { A, B, C, D, E, F }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f)]
);
impl_with_latest_from!(Tag7 { A, B, C, D, E, F, G }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g)]
);
impl_with_latest_from!(Tag8 { A, B, C, D, E, F, G, H }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h)]
);
impl_with_latest_from!(Tag9 { A, B, C, D, E, F, G, H, I }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i)]
);
impl_with_latest_from!(Tag10 { A, B, C, D, E, F, G, H, I, J }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j)]
);
impl_with_latest_from!(Tag11 { A, B, C, D, E, F, G, H, I, J, K }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j), (T11, S11, s11, c11, 'k)]
);
impl_with_latest_from!(Tag12 { A, B, C, D, E, F, G, H, I, J, K, L }
[(T1, S1, s1, c1, 'a), (T2, S2, s2, c2, 'b), (T3, S3, s3, c3, 'c), (T4, S4, s4, c4, 'd),
(T5, S5, s5, c5, 'e), (T6, S6, s6, c6, 'f), (T7, S7, s7, c7, 'g), (T8, S8, s8, c8, 'h),
(T9, S9, s9, c9, 'i), (T10, S10, s10, c10, 'j), (T11, S11, s11, c11, 'k),
(T12, S12, s12, c12, 'l)]
);
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest<T1: Clone, T2: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
) -> impl Stream<Item = (T1, T2)> {
(s1, s2).combine_latest()
}
#[deprecated(since = "1.1.0", note = "Use combine_latest_opt instead")]
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest_optional<T1: Clone, T2: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
) -> impl Stream<Item = (Option<T1>, Option<T2>)> {
combine_latest_opt(s1, s2)
}
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest_opt<T1: Clone, T2: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
) -> impl Stream<Item = (Option<T1>, Option<T2>)> {
(s1, s2).combine_latest_opt()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest<T1, T2, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
f: impl for<'a, 'b> FnMut(&'a T1, &'b T2) -> U,
) -> impl Stream<Item = U> {
(s1, s2).map_latest(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest_opt<T1, T2, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
f: impl for<'a, 'b> FnMut(Option<&'a T1>, Option<&'b T2>) -> U,
) -> impl Stream<Item = U> {
(s1, s2).map_latest_opt(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest3<T1: Clone, T2: Clone, T3: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
) -> impl Stream<Item = (T1, T2, T3)> {
(s1, s2, s3).combine_latest()
}
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest_opt3<T1: Clone, T2: Clone, T3: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
) -> impl Stream<Item = (Option<T1>, Option<T2>, Option<T3>)> {
(s1, s2, s3).combine_latest_opt()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest3<T1, T2, T3, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
f: impl for<'a, 'b, 'c> FnMut(&'a T1, &'b T2, &'c T3) -> U,
) -> impl Stream<Item = U> {
(s1, s2, s3).map_latest(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest_opt3<T1, T2, T3, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
f: impl for<'a, 'b, 'c> FnMut(Option<&'a T1>, Option<&'b T2>, Option<&'c T3>) -> U,
) -> impl Stream<Item = U> {
(s1, s2, s3).map_latest_opt(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest4<T1: Clone, T2: Clone, T3: Clone, T4: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
) -> impl Stream<Item = (T1, T2, T3, T4)> {
(s1, s2, s3, s4).combine_latest()
}
#[must_use = "streams do nothing unless polled"]
pub fn combine_latest_opt4<T1: Clone, T2: Clone, T3: Clone, T4: Clone>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
) -> impl Stream<Item = (Option<T1>, Option<T2>, Option<T3>, Option<T4>)> {
(s1, s2, s3, s4).combine_latest_opt()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest4<T1, T2, T3, T4, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
f: impl for<'a, 'b, 'c, 'd> FnMut(&'a T1, &'b T2, &'c T3, &'d T4) -> U,
) -> impl Stream<Item = U> {
(s1, s2, s3, s4).map_latest(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn map_latest_opt4<T1, T2, T3, T4, U>(
s1: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
f: impl for<'a, 'b, 'c, 'd> FnMut(
Option<&'a T1>,
Option<&'b T2>,
Option<&'c T3>,
Option<&'d T4>,
) -> U,
) -> impl Stream<Item = U> {
(s1, s2, s3, s4).map_latest_opt(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn with_latest_from<T1: Clone, T2: Clone>(
primary: impl Stream<Item = T1>,
secondary: impl Stream<Item = T2>,
) -> impl Stream<Item = (T1, T2)> {
(primary, secondary).with_latest_from()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_with_latest_from<T1, T2, U>(
primary: impl Stream<Item = T1>,
secondary: impl Stream<Item = T2>,
f: impl for<'a, 'b> FnMut(&'a T1, &'b T2) -> U,
) -> impl Stream<Item = U> {
(primary, secondary).map_with_latest_from(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn with_latest_from3<T1: Clone, T2: Clone, T3: Clone>(
primary: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
) -> impl Stream<Item = (T1, T2, T3)> {
(primary, s2, s3).with_latest_from()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_with_latest_from3<T1, T2, T3, U>(
primary: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
f: impl for<'a, 'b, 'c> FnMut(&'a T1, &'b T2, &'c T3) -> U,
) -> impl Stream<Item = U> {
(primary, s2, s3).map_with_latest_from(f)
}
#[must_use = "streams do nothing unless polled"]
pub fn with_latest_from4<T1: Clone, T2: Clone, T3: Clone, T4: Clone>(
primary: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
) -> impl Stream<Item = (T1, T2, T3, T4)> {
(primary, s2, s3, s4).with_latest_from()
}
#[must_use = "streams do nothing unless polled"]
pub fn map_with_latest_from4<T1, T2, T3, T4, U>(
primary: impl Stream<Item = T1>,
s2: impl Stream<Item = T2>,
s3: impl Stream<Item = T3>,
s4: impl Stream<Item = T4>,
f: impl for<'a, 'b, 'c, 'd> FnMut(&'a T1, &'b T2, &'c T3, &'d T4) -> U,
) -> impl Stream<Item = U> {
(primary, s2, s3, s4).map_with_latest_from(f)
}
#[cfg(test)]
mod tests {
use super::*;
use futures::executor::block_on;
use futures::stream::{self, StreamExt};
#[test]
fn combine_latest_basic() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on(combine_latest(s1, s2).collect());
assert_eq!(result, vec![(1, "a"), (2, "a"), (2, "b"), (3, "b")]);
}
#[test]
fn combine_latest_empty_stream() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(Vec::<&str>::new());
let result: Vec<_> = block_on(combine_latest(s1, s2).collect());
assert!(result.is_empty());
}
#[test]
fn combine_latest_opt_basic() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a"]);
let result: Vec<_> = block_on(combine_latest_opt(s1, s2).collect());
assert_eq!(
result,
vec![(Some(1), None), (Some(1), Some("a")), (Some(2), Some("a")),]
);
}
#[test]
fn combine_latest_opt_empty_stream() {
let s1 = stream::iter(Vec::<i32>::new());
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on(combine_latest_opt(s1, s2).collect());
assert_eq!(result, vec![(None, Some("a")), (None, Some("b"))]);
}
#[test]
#[allow(deprecated)]
fn combine_latest_optional_delegates() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let result: Vec<_> = block_on(combine_latest_optional(s1, s2).collect());
let expected: Vec<_> =
block_on(combine_latest_opt(stream::iter([1]), stream::iter(["a"])).collect());
assert_eq!(result, expected);
}
#[test]
fn map_latest_basic() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on(map_latest(s1, s2, |n, s| format!("{n}{s}")).collect());
assert_eq!(result, vec!["1a", "2a", "2b"]);
}
#[test]
fn map_latest_non_clone() {
struct NonClone(i32);
let s1 = stream::iter([NonClone(1), NonClone(2)]);
let s2 = stream::iter([NonClone(10)]);
let result: Vec<_> = block_on(map_latest(s1, s2, |a, b| a.0 + b.0).collect());
assert_eq!(result, vec![11, 12]);
}
#[test]
fn map_latest_opt_basic() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a"]);
let result: Vec<_> =
block_on(map_latest_opt(s1, s2, |n, s| (n.copied(), s.copied())).collect());
assert_eq!(
result,
vec![(Some(1), None), (Some(1), Some("a")), (Some(2), Some("a")),]
);
}
#[test]
fn combine_latest3_basic() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let result: Vec<_> = block_on(combine_latest3(s1, s2, s3).collect());
assert_eq!(result, vec![(1, "a", true), (2, "a", true)]);
}
#[test]
fn combine_latest3_empty_stream() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(Vec::<&str>::new());
let s3 = stream::iter([true]);
let result: Vec<_> = block_on(combine_latest3(s1, s2, s3).collect());
assert!(result.is_empty());
}
#[test]
fn combine_latest_opt3_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter::<[bool; 0]>([]);
let result: Vec<_> = block_on(combine_latest_opt3(s1, s2, s3).collect());
assert_eq!(
result,
vec![(Some(1), None, None), (Some(1), Some("a"), None),]
);
}
#[test]
fn map_latest3_basic() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let result: Vec<_> =
block_on(map_latest3(s1, s2, s3, |n, s, b| format!("{n}-{s}-{b}")).collect());
assert_eq!(result, vec!["1-a-true", "2-a-true"]);
}
#[test]
fn map_latest_opt3_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let result: Vec<_> = block_on(
map_latest_opt3(s1, s2, s3, |n, s, b| (n.copied(), s.copied(), b.copied())).collect(),
);
assert_eq!(result[0], (Some(1), None, None));
assert!(result.len() >= 3);
}
#[test]
fn combine_latest4_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5]);
let result: Vec<_> = block_on(combine_latest4(s1, s2, s3, s4).collect());
assert_eq!(result, vec![(1, "a", true, 0.5)]);
}
#[test]
fn combine_latest4_empty_stream() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter(Vec::<bool>::new());
let s4 = stream::iter([0.5]);
let result: Vec<_> = block_on(combine_latest4(s1, s2, s3, s4).collect());
assert!(result.is_empty());
}
#[test]
fn combine_latest_opt4_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter::<[&str; 0]>([]);
let s3 = stream::iter::<[bool; 0]>([]);
let s4 = stream::iter::<[f64; 0]>([]);
let result: Vec<_> = block_on(combine_latest_opt4(s1, s2, s3, s4).collect());
assert_eq!(result, vec![(Some(1), None, None, None)]);
}
#[test]
fn map_latest4_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5_f64]);
let result: Vec<_> = block_on(
map_latest4(s1, s2, s3, s4, |a, b, c, d| format!("{a}-{b}-{c}-{d}")).collect(),
);
assert_eq!(result, vec!["1-a-true-0.5"]);
}
#[test]
fn map_latest_opt4_basic() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5_f64]);
let result: Vec<_> = block_on(
map_latest_opt4(s1, s2, s3, s4, |a, b, c, d| {
(a.copied(), b.copied(), c.copied(), d.copied())
})
.collect(),
);
assert_eq!(result[0], (Some(1), None, None, None));
}
#[test]
fn trait_combine_latest_2() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on((s1, s2).combine_latest().collect());
assert_eq!(result, vec![(1, "a"), (2, "a"), (2, "b"), (3, "b")]);
}
#[test]
fn trait_combine_latest_opt_3() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter::<[bool; 0]>([]);
let result: Vec<_> = block_on((s1, s2, s3).combine_latest_opt().collect());
assert_eq!(
result,
vec![(Some(1), None, None), (Some(1), Some("a"), None),]
);
}
#[test]
fn trait_map_latest_4() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5_f64]);
let result: Vec<_> = block_on(
(s1, s2, s3, s4)
.map_latest(|a, b, c, d| format!("{a}-{b}-{c}-{d}"))
.collect(),
);
assert_eq!(result, vec!["1-a-true-0.5"]);
}
#[test]
fn trait_map_latest_opt_2() {
let s1 = stream::iter([1, 2]);
let s2 = stream::iter(["a"]);
let result: Vec<_> = block_on(
(s1, s2)
.map_latest_opt(|n, s| (n.copied(), s.copied()))
.collect(),
);
assert_eq!(
result,
vec![(Some(1), None), (Some(1), Some("a")), (Some(2), Some("a")),]
);
}
#[test]
fn trait_combine_latest_5() {
let s1 = stream::iter([1]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5]);
let s5 = stream::iter([42u8]);
let result: Vec<_> = block_on((s1, s2, s3, s4, s5).combine_latest().collect());
assert_eq!(result, vec![(1, "a", true, 0.5, 42u8)]);
}
#[test]
fn trait_combine_latest_8() {
let streams = (
stream::iter([1]),
stream::iter([2]),
stream::iter([3]),
stream::iter([4]),
stream::iter([5]),
stream::iter([6]),
stream::iter([7]),
stream::iter([8]),
);
let result: Vec<_> = block_on(streams.combine_latest().collect());
assert_eq!(result, vec![(1, 2, 3, 4, 5, 6, 7, 8)]);
}
#[test]
fn trait_combine_latest_12() {
let streams = (
stream::iter([1]),
stream::iter([2]),
stream::iter([3]),
stream::iter([4]),
stream::iter([5]),
stream::iter([6]),
stream::iter([7]),
stream::iter([8]),
stream::iter([9]),
stream::iter([10]),
stream::iter([11]),
stream::iter([12]),
);
let result: Vec<_> = block_on(streams.combine_latest().collect());
assert_eq!(result, vec![(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)]);
}
#[test]
fn trait_map_latest_12() {
let streams = (
stream::iter([1]),
stream::iter([2]),
stream::iter([3]),
stream::iter([4]),
stream::iter([5]),
stream::iter([6]),
stream::iter([7]),
stream::iter([8]),
stream::iter([9]),
stream::iter([10]),
stream::iter([11]),
stream::iter([12]),
);
let result: Vec<_> = block_on(
streams
.map_latest(|a, b, c, d, e, f, g, h, i, j, k, l| {
a + b + c + d + e + f + g + h + i + j + k + l
})
.collect(),
);
assert_eq!(result, vec![78]);
}
#[test]
fn with_latest_from_basic() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on(with_latest_from(s1, s2).collect());
assert_eq!(result, vec![(2, "a"), (3, "b")]);
}
#[test]
fn with_latest_from_secondary_empty() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(Vec::<&str>::new());
let result: Vec<_> = block_on(with_latest_from(s1, s2).collect());
assert!(result.is_empty());
}
#[test]
fn with_latest_from_primary_empty() {
let s1 = stream::iter(Vec::<i32>::new());
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> = block_on(with_latest_from(s1, s2).collect());
assert!(result.is_empty());
}
#[test]
fn map_with_latest_from_basic() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(["a", "b"]);
let result: Vec<_> =
block_on(map_with_latest_from(s1, s2, |n, s| format!("{n}{s}")).collect());
assert_eq!(result, vec!["2a", "3b"]);
}
#[test]
fn map_with_latest_from_non_clone() {
struct NonClone(i32);
let s1 = stream::iter([NonClone(1), NonClone(2), NonClone(3)]);
let s2 = stream::iter([NonClone(10)]);
let result: Vec<_> = block_on(map_with_latest_from(s1, s2, |a, b| a.0 + b.0).collect());
assert_eq!(result, vec![12, 13]);
}
#[test]
fn trait_with_latest_from_3() {
let s1 = stream::iter([1, 2, 3]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let result: Vec<_> = block_on((s1, s2, s3).with_latest_from().collect());
assert_eq!(result, vec![(2, "a", true), (3, "a", true)]);
}
#[test]
fn trait_with_latest_from_5() {
let s1 = stream::iter([1, 2, 3, 4, 5, 6]);
let s2 = stream::iter(["a"]);
let s3 = stream::iter([true]);
let s4 = stream::iter([0.5]);
let s5 = stream::iter([42u8]);
let result: Vec<_> = block_on((s1, s2, s3, s4, s5).with_latest_from().collect());
assert!(!result.is_empty());
for (_, s, b, d, u) in &result {
assert_eq!(*s, "a");
assert_eq!(*b, true);
assert_eq!(*d, 0.5);
assert_eq!(*u, 42u8);
}
}
}