我可以在循环的多次迭代中传递相同的可变特征对象而不添加间接寻址吗?
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
特征对象的两个词到寄存器中。
我正在编写一些可以输出到标准输出或文件的代码。基于一些外部条件,我实例化文件或标准输出,然后根据对适当项目的引用创建特征对象:
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
特征对象的两个词到寄存器中。