1

Is there a way to use a Writer and get the content written to this writer back? Something like a BufWriterReader? I've tried this, but doesn't compile:

#[test]
fn write_to_json_test0() {
    let json = get_schema_without_optionals0(); // this is a thing serde_json can (de)serialize
    let buf = Vec::new();
    let writer = BufWriter::new(buf); // buf moves here

    serde_json::to_writer(writer, &json).unwrap(); // writer moves here

    let s = String::from_utf8(writer.into_inner().unwrap()).unwrap(); // compile error, because writer used after move

    assert_eq!(s, json.to_string());
}

There is a pull-request for a BufWriter::buffer(), but that's not available now.

What is the fastest way to test if to_string() and to_writer() returns the same? Do I really need to write to file and read the file to a string?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Markus
  • 512
  • 1
  • 4
  • 21

2 Answers2

4

the fastest way to test if to_string() and to_writer() returns the same

Look at the source code for to_string:

let vec = try!(to_vec(value));
let string = unsafe {
    // We do not emit invalid UTF-8.
    String::from_utf8_unchecked(vec)
};
Ok(string)

Which calls to_vec:

let mut writer = Vec::with_capacity(128);
try!(to_writer(&mut writer, value));
Ok(writer)

Stuck together, it's almost the same as your code:

let mut writer = Vec::with_capacity(128);
to_writer(&mut writer, value)?;
let string = unsafe {
    // We do not emit invalid UTF-8.
    String::from_utf8_unchecked(writer)
};
Ok(string)

It is more more efficient, however:

  • it pre-allocates some space in the Vec
  • it avoids the BufWriter (as Sven Marnach points out)
  • it skips the UTF-8 validity checks

a pull-request for a BufWriter::buffer()

No, you cannot access the buffer of the BufWriter until that pull request is merged and released — that's part of the reason that it's being added!

written to this writer

It does not seem like you fully understand the purpose of BufWriter. It buffers data that you write to it, so that the underlying writer doesn't get it. The data in the buffer is data that the underlying writer hasn't seen. The pull request you cite will not help you.

In fact, you need to be sure to flush the BufWriter to ensure that no data remains in the buffer before you try to parse the underlying Vec as a string. Thankfully, into_inner is already doing that for you.

writer moves here

This is because Serde is following the "C-RW-VALUE" guideline: Generic reader/writer functions take R: Read and W: Write by value. You can avoid giving away ownership of the writer by using Write::by_ref:

#[test]
fn write_to_json_test0() -> Result<(), io::Error> {
    let json = Value::Number(42.into());

    let buf = Vec::new();
    let mut writer = BufWriter::new(buf);
    serde_json::to_writer(writer.by_ref(), &json)?;
    let s = String::from_utf8(writer.into_inner()?).unwrap();

    assert_eq!(s, json.to_string());
    Ok(())
}

This works because by_ref returns a mutable reference to the writer, and there's a blanket implementation of Write for any mutable reference to a type that itself implements Write:

impl<'a, W: Write + ?Sized> Write for &'a mut W

by_ref is just a convenience function to avoid the relatively strange syntax of taking a mutable reference explicitly. This pattern of by_ref is repeated for Read as well as Iterator.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • The answer would be short and simple, that there is a `by_ref()` ;-) And it would be more precise to tell that the buffer of the BufWriter is not my Vec, and that's the cause why the pull request will not help here. Do you like to answer questions here? I'm not sure, but you sound a little bit top-down. I'm pretty sure you know everything about Rust and there is no need to tell anyone what you think he/she doesn't know... – Markus Oct 26 '18 at 15:56
  • 1
    @Markus Please, take a step back. Shepmaster is spending a significant amount of his time helping other people on StackOverflow _for free_. It's hard to read the tone of something that an unknown person has written on the internet, but if in doubt, please assume good faith, in particular when someone is trying to help you. – Sven Marnach Oct 26 '18 at 16:00
  • 4
    @Markus Some programmers care to understand the *why* behind a solution, some programmers care to understand that there's a common pattern that applies in multiple cases, and some programmers care to understand where their reasoning went incorrect. These aren't true for every programmer. That being said, Stack Overflow isn't about answering a question solely for the asker, it's about providing broadly applicable answers so that a vast swath of programmers learn, improve, and never need to ask the same question again. TL;DR: "different strokes for different folks". – Shepmaster Oct 26 '18 at 16:05
  • 1
    @SvenMarnach thanks! I don't think Markus is intending to be negative here — my answers often do provide a lot of correlating evidence and links, as well as going up and down the levels of abstraction a bit. Some people come to SO for the quick copy-and-paste solution to a problem and others want the long-form text. Both approaches are absolutely fine. – Shepmaster Oct 26 '18 at 16:11
  • @Shepmaster I understand that the assumption of good faith works both ways. :) I do think that Markus was trying to criticise your tone when saying "you sound a little bit top-down", but my apologies if I'm wrong. – Sven Marnach Oct 26 '18 at 16:18
  • Good folk ;-) Thanks. I've up-to-now from time to time my difficulties to find the right functions and arg-passing. Not so easy to find the way in the Rust world. But once I've found my way the code is great _smile_ – Markus Oct 26 '18 at 16:34
2

There is a forwarding implementation of Write for mutable references to Write:

impl<'a, W: Write + ?Sized> Write for &'a mut W

Using this implementation, you can avoid either moving buf or moving writer, by instead just moving a mutable reference.

Note that wrapping a Vec<u8> in a BufWriter isn't really useful. Buffering is meant to reduce the number of writes to a slow backend, but if your backend is memory anyway, you don't gain anything by adding a layer of buffering.

If we remove the BufWriter, your code could look like this:

fn write_to_json_test0() {
    let json = get_schema_without_optionals0();
    let mut buf = Vec::new();
    serde_json::to_writer(&mut buf, &json).unwrap();
    let s = String::from_utf8(buf).unwrap();
    assert_eq!(s, json.to_string());
}
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • I know it doesn't make much sense to buffer memory, but the API needs a writer. – Markus Oct 26 '18 at 15:41
  • @Markus And so does `BufWriter`. Everything you can pass to `BufWriter::new()` is a writer. – Sven Marnach Oct 26 '18 at 15:43
  • @Markus [`impl Write for Vec`](https://doc.rust-lang.org/std/io/trait.Write.html#impl-Write-9). – Shepmaster Oct 26 '18 at 15:46
  • @Shepmaster yes, but to_writer doesn't accept the Vec, I've tried that before. – Markus Oct 26 '18 at 16:10
  • 1
    @Markus It should accept it. You may need to make the type explicit (`let mut buf: Vec = …`) depending on the context. – Sven Marnach Oct 26 '18 at 16:13
  • @Markus it certainly should... if you check my last update, you can see that serde_json passes a `Vec` to `to_writer` in its implementation of `to_string` :-) – Shepmaster Oct 26 '18 at 16:18
  • Ah yes, sorry, I've forgotten the mut in &mut and the compiler told me only about the as std::io::Write>. – Markus Oct 26 '18 at 16:29