Can I pass the same modified object object in multiple iterations of the loop without adding indirection?
I am writing some code that can output to stdout or to a file. Based on some external state, I instantiate a file or stdout and then create a tag object from a reference to the corresponding element:
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);
}
}
This works great until I try to call it write_it
multiple times. This will fail as it t
moves to write_it
and is therefore not available on subsequent iterations of the loop:
<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
I can get around this by adding another layer of indirection:
let mut t: &mut io::Write;
write_it(&mut t);
But it looks like it could be potentially ineffective. Is it really ineffective? Is there a cleaner way of writing this code?
You need to explicitly iterate over:
for _ in 0..10 { write_it(&mut *t); }
This often happens implicitly, but it is not, because it write_it
takes the raw pedigree W
, and the compiler only implicitly reborrows a &mut
when used in the place it expects &mut
. For example. if it was
fn write_it<W: ?Sized + Write>(w: &mut W) { ... }
your code works fine, since being explicit &mut
in the argument type ensures that the compiler will implicitly redraw with a shorter lifetime (i.e. &mut*
).
Cases like this demonstrate that &mut
ownership does indeed translate, implicit reallocation often disguises it in favor of improved ergonomics.
As for the performance of the side-referenced version: the speed &mut (&mut Write)
will probably be indistinguishable from the simple &mut Write
: a virtual call will usually be much more expensive than dereferencing &mut
.
In addition, the anti-aliasing guarantees &mut
mean that the compiler is very free from how it interacts with &mut
: for example, depending on the internals, it might load two words &mut Write
from a pointer into registers once at the beginning write_it
, and then write any changes at the end. This is legal because it &mut
means that there is nothing that can mutate this memory.
Finally, at this point, a "large" value is passed through the pointer, such as &mut Write
; essentially the same as &mut &mut Write
on the machine. Build for both versions &mut *t
and &mut t
runs (literally the only difference I can see is the label names 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 ...
The two movq
at the end load two object object words &mut Write
into registers.