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?

+3


source to share


1 answer


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.

+4


source







All Articles