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 an implementation of TryStream::try_zip #1598

Closed
wants to merge 2 commits into from

Conversation

shepmaster
Copy link
Member

Closes #1593

@shepmaster
Copy link
Member Author

This makes the decision to "throw away" one of the errors if both streams report a failure for the same zipper tooth. This may or may not be desired... ?

cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Ok, Self::Error>>> {
if self.queued1.is_none() {
match self.as_mut().stream1().try_poll_next(cx) {
Copy link
Member

Choose a reason for hiding this comment

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

I think using self.as_mut().stream1().try_poll_next(cx)? here will avoid the problem you mentioned.

Copy link
Member Author

@shepmaster shepmaster May 11, 2019

Choose a reason for hiding this comment

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

Wouldn't that cause the two halves of the zipper to become unaligned? That is,

A: Ok(1), Err(2), Ok(3)
B: Ok(4), Ok(5), Ok(6)

Would result in

Ok((1, 4)), Err(2), Ok((3,5))

Is that desired? (Perhaps this also suggests why this combinator hasn't been created before now).

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

the same behavior as Stream::zip of futures 0.1

It sure is:

use futures::{future, stream, Future, Stream}; // 0.1.26

fn main() {
    let a: Vec<Result<i32, i32>> = vec![Ok(1), Err(2), Ok(3)];
    let b: Vec<Result<i32, i32>> = vec![Ok(4), Ok(5), Ok(6)];

    stream::iter_result(a)
        .zip(stream::iter_result(b))
        .then(|x| future::ok::<_, ()>(eprintln!("{:?}", x)))
        .for_each(|_| Ok(()))
        .wait();
}

(Playground)

Output:

Ok((1, 4))
Err(2)
Ok((3, 5))

But I think the question remains: is this the expected and desired behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

is this the expected and desired behavior?

If we are open to changing behaviors, the most comprehensive solution is to have

enum ZipError<A, B> {
    A(A),
    B(B),
    Both(A, B)
}

Copy link
Member

Choose a reason for hiding this comment

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

Hmm... I have no clear opinion on which is better, for now.

FWIW, as another thing we may need to consider, the behavior of current this PR's TryZip can be written using Zip, but the behavior of 0.1's Zip cannot be written using either 0.3's Zip or current this PR's TryZip.

cc @cramertj @Nemo157 Do you have any thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I would've personally expected that try_zip would stop returning at all after the first error, but I don't have a strong opinion here.

Copy link
Member

Choose a reason for hiding this comment

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

Bump-- @shepmaster what are your thoughts here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sadly, I don't even remember what my original use case was for this, and I don't recall what my thoughts were. 😊

I've posted the open questions back on the issue. The fact that there aren't people clamoring for this feature is perhaps a sign that it's not useful, yet.

Copy link
Member

Choose a reason for hiding this comment

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

Okay! Let me know if you're interested in revisiting at some point.

queued2: Option<Result<St2::Ok, St2::Error>>,
}

impl<St1: TryStream + Unpin, St2: TryStream + Unpin> Unpin for TryZip<St1, St2> {}
Copy link
Member

Choose a reason for hiding this comment

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

This should technically be

impl<St1: TryStream, St2: TryStream> Unpin for TryZip<St1, St2>
where
    Fuse<IntoStream<St1>>: Unpin,
    Fuse<IntoStream<St2>>: Unpin,
{}

In case either Fuse or IntoStream don’t have the equivalent Unpin implementations.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, but it's unfortunate that the internal implementation details need to leak out like that.

Should the impl Unpin for Zip be updated to use Fuse as well? Is this a broader issue?

Copy link
Member

Choose a reason for hiding this comment

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

it's unfortunate that the internal implementation details need to leak out like that.

Yeah, that was my feeling as well as I wrote the comment. I rationalized it as being the same as the auto-trait's structural rules, if TryZip were to rely on the implicit Unpin you would still get error messages saying "Fuse does not implement Unpin".

Is this a broader issue?

Probably, I'll try to remember to open a ticket about it. (This is one of the reasons I prefer pin-project for projection, it can derive the Unpin implementation for you so that you don't have to keep it consistent with the current field definitions).

Copy link
Member

Choose a reason for hiding this comment

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

I was just looking into this a little more as I encountered the situation in another library where the Unpin definition depends on a crate internal type. It looks like rustdoc has special handling for the automatic implementations that looks through the field types and shows the bounds that result from them instead of revealing the internal types, it would be nice if there were some way to request that to happen for manual implementations as well.

For example

struct Foo<T>(T);

pub struct Bar<T>(Foo<T>);

unsafe impl<T: Send + Iterator> Send for Foo<T> {}

shows in the docs

impl<T> Send for Bar<T>
where
    T: Iterator + Send, 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should TryStream::try_zip exist?
4 participants