我正在编写一些可以输出到标准输出或文件的代码。根据某些外部条件,我实例化文件或标准输出,然后从对适当项目的引用创建一个特征对象:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我可以通过添加另一层间接来解决这个问题:
let mut t: &mut io::Write;
write_it(&mut t);
Run Code Online (Sandbox Code Playgroud)
但这似乎可能效率低下。实际上效率低下吗?有没有更干净的方式来编写这段代码?
您需要明确地重新借用:
for _ in 0..10 {
write_it(&mut *t);
}
Run Code Online (Sandbox Code Playgroud)
人们经常看到这种情况隐式发生,但在这种情况下并非如此,因为write_it采用原始泛型 ,W并且编译器仅&mut在需要 的地方使用时才隐式重新借用 a &mut。例如,如果是
fn write_it<W: ?Sized + Write>(w: &mut W) { ... }
Run Code Online (Sandbox Code Playgroud)
您的代码工作正常,因为&mut参数类型中的显式将确保编译器将隐式重新借用较短的生命周期(即 )&mut*。
像这样的案例表明,&mut实际上确实转移了所有权,隐性的重新借用通常会掩盖它以支持改进的人体工程学。
至于具有额外引用的版本的性能: a 的速度&mut (&mut Write)可能与普通的 没有区别&mut Write:虚拟调用通常比取消引用&mut.
此外, 的别名保证&mut意味着编译器对于如何与 a 交互非常自由:例如,根据内部原理,它可以在 a 开始时将指针中&mut的两个字加载到寄存器中,然后将任何更改写回结束。这是合法的,因为成为一个意味着没有其他东西可以改变该记忆。&mut Writewrite_it&mut
最后,目前,像 a 这样的“大”值&mut Write是通过指针传递的;&mut &mut Write与机器上的基本相同。&mut *t和版本的程序集&mut t都会启动(实际上我能看到的唯一区别是标签的名称Ltmp...):
_ZN8write_it20h2919620193267806634E:
.cfi_startproc
cmpq %fs:112, %rsp
ja .LBB4_2
movabsq $72, %r10
movabsq $0, %r11
callq __morestack
retq
.LBB4_2:
pushq %r14
.Ltmp116:
.cfi_def_cfa_offset 16
pushq %rbx
.Ltmp117:
.cfi_def_cfa_offset 24
subq $56, %rsp
.Ltmp118:
.cfi_def_cfa_offset 80
.Ltmp119:
.cfi_offset %rbx, -24
.Ltmp120:
.cfi_offset %r14, -16
movq (%rdi), %rsi
movq 8(%rdi), %rax
...
Run Code Online (Sandbox Code Playgroud)
末尾的两个movqs 将特征对象的两个字加载&mut Write到寄存器中。