3

I'm trying to create a Writer instance either from standard output or from a file freshly created (if a path is provided), then use that instance to write to it.

The problem is that I'm unable to assign it using a match expression:

let file;
let stdout = stdout();

// argpath, when not None, contains some path to a file to create 
let mut writer = match argpath {
    Some(path) => {
        file = File::create(path)?;
        BufWriter::new(file)
    },
    None => {
        BufWriter::new(stdout.lock())
    }
};

writeln!(writer, "Blah");

The compiler complains obviously, as the two arms of the match do not return the same object, BufWriter<File> and BufWriter<StdoutLock>:

error[E0308]: `match` arms have incompatible types
   --> src/main.rs:115:13
    |
109 |       let mut writer = match argpath {
    |  ______________________-
110 | |         Some(path) => {
111 | |             file = File::create(path)?;
112 | |             BufWriter::new(file)
    | |             -------------------- this is found to be of type `std::io::BufWriter<File>`
...   |
115 | |             BufWriter::new(stdout.lock())
    | |             ^^^^^^^^^^^^^^^^^^^^^^ expected struct `File`, found struct `StdoutLock`
116 | |         }
117 | |     };
    | |_____- `match` arms have incompatible types
    |
    = note: expected type `std::io::BufWriter<File>`
             found struct `std::io::BufWriter<StdoutLock<'_>>`
note: return type inferred to be `std::io::BufWriter<File>` here

In general, is there an existing programming pattern, in Rust, that allows to assign a BufWriter<> to a variable, irrespective of the inner type, so the following code can consume it as a regular object implementing the Write traits?

keldonin
  • 314
  • 1
  • 10
  • Dynamic dispatching can also be avoided by calling a generic function or method with those different values in each branch, instead of assigning them to the same variable. (`if something { do_stuff(BufWriter::new(file)) } else { do_stuff(stdout) }`) – E_net4 Jan 06 '22 at 12:46
  • @E_net4thecurator, thank you for the comment! The link you provided is interesting, but is not entirely addressing the question, as it implies that the "base" component is a trait. In this case, it is not; `BufWriter` is a struct defined in `std::io`. I suppose the second comment you make implies `do_stuff()` is a generic function. While this would circumvent the issue, it would force a significant overhaul of the code that follows. – keldonin Jan 06 '22 at 13:08
  • [The duplicate applied to your situation.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9b027c529777b7fec2c24dfdcc75c61a) – Jmb Jan 06 '22 at 13:56
  • It's worth noting that `stdout` is line buffered by default, so if you don't expect to be writing small newline terminated strings the external buffer can hurt performance. – Aiden4 Jan 06 '22 at 23:32

1 Answers1

2

You need to dynamically dispatch it. For doing so, usually you wrap them in Box<dyn Trait>:

let mut writer: BufWriter<Box<dyn Write>> = match argpath {
    Some(path) => {
        file = File::create(path)?;
        BufWriter::new(Box::new(file))
    },
    None => {
        BufWriter::new(Box::new(stdout.lock())
    }
};

Playground

keldonin
  • 314
  • 1
  • 10
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Well, `BufWriter` is a struct, not a trait. – keldonin Jan 06 '22 at 12:44
  • @keldonin, yeah, I realized after publishing it hehe, but thanks for noticing anyway :) – Netwave Jan 06 '22 at 12:45
  • 1
    Doing it like this is unnecessarily inefficient. Since you need BufWriter in both arms, you can make writer a `BufWriter>` and create it with `BufWriter::new(Box::new(file) as Box)`. That way small writes (such as those issued by serde) that write to the buffer are inlined and don't incur the cost of dynamic dispatch. Only when the buffer is being flushed does the dynamic dispatch occur, at which point it will be dwarfed by the cost of actual IO. – user4815162342 Jan 06 '22 at 13:28
  • @user4815162342, thanks, that seems to be the right approach. In fact, type coercion `as Box` is not even needed! – keldonin Jan 06 '22 at 13:47
  • @user4815162342, yep, you are right. Much better that way. @keldonin, you would need either adding the type to writer or doing the `as` coercion. Just added both to make a clear example. – Netwave Jan 06 '22 at 13:56
  • 1
    @user4815162342, oh, no problem, I don't usually worry about that too much. I'm just here to learn and help :), thanks for everything! – Netwave Jan 06 '22 at 13:59
  • I don't have enough reputation to vote the answer up. Anyone is welcome... – keldonin Jan 06 '22 at 14:14
  • @keldonin But, as the person who posted the question, you can [accept the answer](https://meta.stackexchange.com/a/5235/627709) (and should do so if the issue is resolved). – user4815162342 Jan 06 '22 at 14:58
  • @user4815162342 thanks for the hint! – keldonin Jan 06 '22 at 19:15