为什么serde_json::to_writer不需要将其参数标记为`mut`?

3

我看到了这个问题,解释了serde_json是如何通过引用或取得控制权来读取和写入Readers/Writers的。看起来很好理解。

但对于Write,我不明白它是如何工作的 - 所有Write方法都需要一个&mut self, 因此我认为如果我传递一个只知道其参数是引用到某些东西的方法,它无法使用它。但是,即使我向一个只接受非可变引用的方法传递了一个方法,它也可以编译并正常工作,尽管我正在将一个非可变引用传递给一种以某种方式写入所引用文件的方法:

extern crate serde_json;

use std::fs::OpenOptions;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open("/tmp/serde.json")?;
    // why does this work?
    serde_json::to_writer(&file, &10)?;
    Ok(())
}

我正在传递一个 &File - 如预期的那样,如果我直接在一个 File 上调用任何 Write 的方法,它是不起作用的:

use std::io::{self, Write};
use std::fs::OpenOptions;

fn main() -> io::Result<()> {
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open("/tmp/wtf")?;
    let file_ref = &file;
    // this complains about not having a mutable ref as expected
    file_ref.write(&[1,2,3])?;
    Ok(())
}

error[E0596]: cannot borrow `file_ref` as mutable, as it is not declared as mutable
  --> test.rs:12:5
   |
10 |     let file_ref = &file;
   |         -------- help: consider changing this to be mutable: `mut file_ref`
11 |     // this complains about not having a mutable ref as expected
12 |     file_ref.write(&[1,2,3])?;
   |     ^^^^^^^^ cannot borrow as mutable

那么问题出在哪里呢?是serde_json破坏了类型系统,还是这是类型系统的一个有意的特性?如果是后者,它如何工作,为什么会这样工作呢?
1个回答

11

serde_json::to_writer的第一个参数接受任何实现Write的类型。其中一种值是&File File

这可能令人惊讶,但是 File 的文档明确指出文件的引用的可变性与文件是否更改无关:

请注意,尽管读取和写入方法需要&mut File,但由于ReadWrite的接口缘故,持有&File的人仍然可以通过取&File的方法或检索底层操作系统对象并以那种方式修改文件来修改文件。此外,许多操作系统允许不同进程对文件进行并发修改。避免假设持有&File意味着文件不会更改。

这可能会让您问 - 等等,我以为 Write 上的方法使用&mut self?他们确实这样做!但在这种情况下,Write是为&File实现的,因此传递给Write的类型有点令人困惑,是 &mut&File(对不可变引用的可变引用)。

这解释了为什么您的第二个示例无法编译 - 您需要能够使用可变绑定来获取&file的可变引用,否则无法进行该操作。这是一个重要的事情要意识到 - 绑定的可变性和绑定值的可变性并不一定相同。

当您运行代码时,错误消息中暗示了这一点:

error[E0596]: cannot borrow `file_ref` as mutable, as it is not declared as mutable
  --> src/lib.rs:11:5
   |
10 |     let file_ref = &file;
   |         -------- help: consider changing this to be mutable: `mut file_ref`
11 |     file_ref.write(&[1,2,3])?;
   |     ^^^^^^^^ cannot borrow as mutable

error: aborting due to previous error

如果你把let file_ref = &file;改成let mut file_ref = &file;,你的代码就能够编译。


1
非常好的解释!值得注意的是,当编写一个接受 ReadWrite 的通用函数时,应该始终按值传递,而不是按引用传递,就像 serde 一样。 - rodrigo
1
是的 - 提问者链接的原始问题也有这个答案 :) - Joe Clay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接