我可以在循环的多次迭代中传递相同的可变特征对象而不添加间接寻址吗?

Can I pass the same mutable trait object in multiple iterations of a loop without adding indirection?

我正在编写一些可以输出到标准输出或文件的代码。基于一些外部条件,我实例化文件或标准输出,然后根据对适当项目的引用创建特征对象:

use std::{io,fs};

fn write_it<W>(mut w: W) where W: io::Write { }

fn main() {
    let mut stdout;
    let mut file;

    let t: &mut io::Write = if true {
        stdout = io::stdout();
        &mut stdout
    } else {
        file = fs::File::create("/tmp/output").unwrap();
        &mut file
    };

    for _ in 0..10 {
        write_it(t);
    }
}

这很好用,直到我多次尝试调用 write_it。这将失败,因为 t 被移动到 write_it 中,因此在循环的后续迭代中不可用:

<anon>:18:18: 18:19 error: use of moved value: `t`
<anon>:18         write_it(t);
                           ^
note: `t` was previously moved here because it has type `&mut std::io::Write`, which is non-copyable

我可以通过添加另一层间接来解决它:

let mut t: &mut io::Write;
write_it(&mut t);

但这似乎可能效率低下。它实际上是低效的吗?有没有更简洁的代码编写方式?

您需要明确地重新借用:

for _ in 0..10 {
    write_it(&mut *t);
}

人们经常看到这种隐式发生,但在这种情况下并非如此,因为 write_it 采用原始泛型 W,并且编译器仅在使用时隐式重新借用 &mut在一个期待 &mut 的地方。例如。如果是

fn write_it<W: ?Sized + Write>(w: &mut W) { ... }

您的代码工作正常,因为参数类型中的显式 &mut 将确保编译器将隐式重新借用较短的生命周期(即 &mut*)。

像这样的案例表明 &mut 确实转移了所有权,隐式的重新借用经常伪装它以支持改进的人体工程学。

至于带有额外引用的版本的性能:&mut (&mut Write) 的速度可能与普通 &mut Write 没有区别:虚拟调用通常比取消引用昂贵得多&mut.

此外,&mut 的别名保证意味着编译器在如何与 &mut 交互方面非常自由:例如,根据内部结构,它可能会加载 [=] 的两个词21=] 在 write_it 开始时从指针进入寄存器一次,然后在末尾写回任何更改。这是合法的,因为成为 &mut 意味着没有其他东西可以改变那段记忆。

最后,目前,像 &mut Write 这样的 "large" 值是通过指针传递的;与机器上的 &mut &mut Write 基本相同。 &mut *t&mut t 版本的程序集都开始(从字面上看,我能看到的唯一区别是 Ltmp... 标签的名称):

_ZN8write_it20h2919620193267806634E:
    .cfi_startproc
    cmpq    %fs:112, %rsp
    ja  .LBB4_2
    movabsq , %r10
    movabsq [=12=], %r11
    callq   __morestack
    retq
.LBB4_2:
    pushq   %r14
.Ltmp116:
    .cfi_def_cfa_offset 16
    pushq   %rbx
.Ltmp117:
    .cfi_def_cfa_offset 24
    subq    , %rsp
.Ltmp118:
    .cfi_def_cfa_offset 80
.Ltmp119:
    .cfi_offset %rbx, -24
.Ltmp120:
    .cfi_offset %r14, -16
    movq    (%rdi), %rsi
    movq    8(%rdi), %rax
    ...

最后的两个movq正在加载&mut Write特征对象的两个词到寄存器中。