bytes icon indicating copy to clipboard operation
bytes copied to clipboard

implement Write trait for BytesMut per issue #425

Open rich-murphey opened this issue 3 years ago • 11 comments

There is some discussion of this in #425.

This implements Write for BytesMut exactly as is done for Vec here:

https://doc.rust-lang.org/src/std/io/impls.rs.html#370-402

rich-murphey avatar Feb 07 '21 04:02 rich-murphey

Could we add some tests?

carllerche avatar Feb 07 '21 17:02 carllerche

Because BufMut already implements fmt::Write, this change will break any code that uses write!() with a BytesMut while both fmt::Write and io::Write are imported.

sfackler avatar Feb 07 '21 17:02 sfackler

@sfackler good catch!

we should add a test to cover this edge case.

carllerche avatar Feb 07 '21 18:02 carllerche

At the moment, I'm not sure how to proceed. I don't know how to avoid breaking write!() as @sfackler said, but wow, I'm so glad he caught it.

rich-murphey avatar Feb 07 '21 20:02 rich-murphey

I don't see a way to proceed with this. It isn't great, but one can always use BufMut::writer() to adapt.

I would appreciate a test that demonstrates the ability to call write! with both mentioned traits in scope. You can reuse this PR or submit a new one.

carllerche avatar Feb 08 '21 17:02 carllerche

I think you could in theory add an inherent write_fmt method directly on impl BytesMut since that would take precedence over either of the trait methods. It's a bit gross though.

sfackler avatar Feb 08 '21 18:02 sfackler

Thanks so much for the directions @sfackler and @carllerche. I'll write some tests so we can evaluate them.

rich-murphey avatar Feb 08 '21 19:02 rich-murphey

I tried adding write_fmt() directly on impl BytesMut, and still saw compiler errors when write!() saw both io::Write and fmt::Write impls.

So here's another option. This adds a BytesMutExt trait that implements io::Write.

It's a bit awkward to use though:. The successful test (included) looks like this:

#[test]
fn fmt_write_bytesmut_with_both_write_in_scope_test() {
    use bytes::ext::BytesMutExt;
    use bytes::BytesMut;
    #[allow(unused_imports)]
    use std::fmt::Write as FmtWrite;
    use std::io::Write as IoWrite;
    let mut buf = BytesMut::with_capacity(64);
    write!(&mut buf as &mut dyn BytesMutExt, "hello").ok();
    assert_eq!(&buf[..], b"hello");
}

This works, but it's awkward. I'm not sure this is helpful at all.

Please let me know if I've misunderstood the idea of having a write_fmt() directly in impl BytesMut.

And if you think this is a dead horse, that 's ok too.

rich-murphey avatar Feb 09 '21 11:02 rich-murphey

Found my way here from the issue. Did you consider something like the following?

write!(buf.as_io_write(), "hello")

I think this is more readable than write!(&mut buf as &mut dyn BytesMutExt, "hello") and doesn't use dynamic dispatch.

The problem I have with the current BufMut::writer is that it consumes the BytesMut which doesn't work when you don't own the it. Eg in a tokio_util::codec::Encoder I have to std::mem::replace out the BytesMut then put it back afterward:

https://github.com/scottlamb/retina/blob/265b6b94901d02b03e51d2b6f3338de899077687/src/lib.rs#L360-L363

fn as_io_write(&mut self) -> IoWriteRef<'_> (or similar) wouldn't have this problem.

scottlamb avatar Jun 29 '21 19:06 scottlamb

You could do write!((&mut buf).writer(), "hello"), still I do see the point.

Darksonn avatar Jun 29 '21 19:06 Darksonn

Oh, I didn't think of that. Thanks for the tip! Actually, I would have been perfectly happy if that example were just in writer()'s docstring.

scottlamb avatar Jun 29 '21 19:06 scottlamb