6

I'm trying to write a function that calls serde_json::to_writer twice to write two things, but I can't figure out how.

Here's one attempt (playground link):

extern crate serde_json;

fn main() {
    let mut out = std::fs::File::create("out.txt").unwrap();
    write_stuff(&mut out);
}

fn write_stuff<W: ?Sized>(out: &mut W) -> Result<(), std::io::Error>
where
    W: std::io::Write,
{
    serde_json::to_writer(out, &1).unwrap();  // `out`: value moved here
    serde_json::to_writer(out, &2).unwrap();  // `out`: value used here after move
    Ok(())
}

Edit: Figured out a way to get things to compile, but is there an easier way (playground link):

extern crate serde_json;

fn main() {
    let mut out = std::fs::File::create("out.txt").unwrap();
    write_stuff(&mut out);
}

fn write_stuff<W: ?Sized>(out: &mut W)
where
    W: std::io::Write,
{
    write_wrapper(out);
    write_wrapper(out);
}

fn write_wrapper<W: ?Sized>(out: &mut W)
where
    W: std::io::Write,
{
    serde_json::to_writer(out, &1).unwrap();  
}
Kannan Goundan
  • 4,962
  • 3
  • 24
  • 31
  • 1
    Possible duplicate of [Why does serde\_json::from\_reader take ownership of the reader?](https://stackoverflow.com/questions/51809603/why-does-serde-jsonfrom-reader-take-ownership-of-the-reader) So, you just need to call serde_json::to_writer(&mut out, &1). – harmic Oct 10 '19 at 06:53
  • 1
    I don't think this is an exact duplicate. In the other question, the problem is that the function takes ownership, and we need to create a borrow. Here, `out` already _is_ a mutable borrow, but the compiler does not create an implicit reborrow, as it usually does when passing on mutable references to function calls, so we need to explicitly reborrow `out`. – Sven Marnach Oct 11 '19 at 07:29

1 Answers1

3

The reason for this behaviour is somewhat subtle. When passing a shared reference as parameter to a function, Rust will simply copy the reference. The type &T is Copy for all T, since we are allowed to have any number of shared references at the same time.

Mutable references, on the other hand, are not Copy, since there can be only one of them at any given time. According to the usual Rust semantics for non-Copy types, this means that mutable references should be moved when passed as a parameter. So why does this code work?

fn foo(_: &mut i32) {}

fn main() {
    let mut i = 42;
    let r = &mut i;
    foo(r);
    foo(r);
}

The reason is that the compiler creates an implicit reborrow whenever it assigns to a variable that is explicitly declared as a mutable reference, so the function calls are translated to foo(&mut *r). This creates a new borrow that only lasts for the duration of the function call, and the original reference r becomes available again once the lifetime of the reborrow ends.

However, implicit reborrows are only generated for variables that are explicitly declared with a mutable reference type. If we change the definition of foo() above to

fn foo<T>(_: T) {}

the code will stop compiling. Now the parameter of foo() is not declared as a mutable reference anymore, so the compiler will not introduce an implicit reborrow, and will move ownership of r into the function in the first call instead, resulting in an error in the second function call.

The same thing happens in your code. The function to_writer() is declared as

pub fn to_writer<W, T: ?Sized>(writer: W, value: &T) -> Result<()>
where
    W: io::Write,
    T: Serialize,

Since the argument writer is not declared as a mutable reference, you need to create an explicit reborrow to avoid a move:

serde_json::to_writer(&mut *out, &1)?;

The alternative solution you gave in your question works as well – the write_wrapper() function receives an explicitly declared mutable reference as argument, so calls to this function trigger implicit reborrows.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841