Search code examples
structrustownershiptrait-objects

A struct with a trait field, but optional


Say I have a struct whose implementation writes somewhere, i.e. to something that implements the std::io::Write trait. However, I don't want the struct to own this. The following code works:

fn main() {
    let mut out = std::io::stdout();
    let mut foo = Foo::new(&mut out);
    foo.print_number(2);
}

struct Foo<'a> {
    out: &'a mut dyn std::io::Write
}

impl<'a> Foo<'a> {
    pub fn new(out: &'a mut dyn std::io::Write) -> Self {
        Self {
            out
        }
    }
    
    pub fn print_number(&mut self, i: isize) {
        writeln!(self.out, "The number is {}", i).unwrap()
    }
}

But, now this writing functionality should be made optional. I thought this sounds easy enough, but now the following doesn't compile:

fn main() {
    let mut out = std::io::stdout();
    let mut foo = Foo::new(Some(&mut out));
    foo.print_number(2);
}

struct Foo<'a> {
    out: Option<&'a mut dyn std::io::Write>
}

impl<'a> Foo<'a> {
    pub fn new(out: Option<&'a mut dyn std::io::Write>) -> Self {
        Self {
            out
        }
    }
    
    pub fn print_number(&mut self, i: isize) {
        if self.out.is_some() {
            writeln!(self.out.unwrap(), "The number is {}", i).unwrap()
        }
    }
}

because of:

error[E0507]: cannot move out of `self.out` which is behind a mutable reference
        --> src/main.rs:20:26
        |
        20 |                 writeln!(self.out.unwrap(), "The number is {}", i).unwrap()
        |                          ^^^^^^^^
        |                          |
        |                          move occurs because `self.out` has type `Option<&mut dyn std::io::Write>`, which does not implement the `Copy` trait
        |                          help: consider borrowing the `Option`'s content: `self.out.as_ref()`

which I'm not sure how to interpret.

I tried following the suggestion by changing the line in question to:

writeln!(self.out.as_ref().unwrap(), "The number is {}", i).unwrap()

but then I get

error[E0596]: cannot borrow data in a `&` reference as mutable
--> src/main.rs:20:26
|
20 |                 writeln!(self.out.as_ref().unwrap(), "The number is {}", i).unwrap()
|                          ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

I'm really not sure how to interpret these error messages and surprisingly I'm not really getting anywhere by just sprinkling &s and muts in random places without really understanding!

(As an aside, I'm not sure if this is a "good" way of going about this anyway? I'm open to completely different approaches of solving this problem, which is basically to optionally pass something to write to into a struct, but without the struct owning it. I read about the Box type which might also be relevant?)


Solution

  • As you already know, based on you already using &mut for out. The issue with using as_ref() is that it returns an immutable reference. Instead you need to use as_mut().

    pub fn print_number(&mut self, i: isize) {
        if self.out.is_some() {
            writeln!(self.out.as_mut().unwrap(), "The number is {}", i).unwrap()
        }
    }
    

    Alternatively, you can also simplify this and express it more idiomatically using if let:

    pub fn print_number(&mut self, i: isize) {
        if let Some(out) = &mut self.out {
            writeln!(out, "The number is {}", i).unwrap()
        }
    }
    

    I would also suggest that instead of unwrapping, that you return the io::Result and let the caller handle any potential error.

    pub fn print_number(&mut self, i: isize) -> std::io::Result<()> {
        if let Some(out) = &mut self.out {
            writeln!(out, "The number is {}", i)?;
        }
        Ok(())
    }
    

    You can also simplify your paths, e.g. std::io::Write and std::io::Result<()>, by importing them with a use declaration, e.g. use std::io::{self, Write}; and then changing them to Write and io::Result<()>.