Search code examples
rustrust-2021

`match` arms have incompatible types


I have some code that needs to create and use a quick_xml::Writer with either a File or Cursor depending on user input. What is the proper Rust way (non-inheritance/no downcasting) to create a Writer with different structs but same trait?

All the answers on stack overflow seem to be old and recommend allocating on the heap with Box, but this no longer works, and personally it feels wrong anyway.

Related but outdated now: How do I overcome match arms with incompatible types for structs implementing same trait?

Code:

use std::fs::File;
use std::io::Cursor;
use quick_xml::Writer;

fn main() {
    let some_option = Some("some_file.txt");

    let writer = match &some_option {
        Some(file_name) => {
            Writer::new(File::create(file_name).unwrap())
        },
        _ => {
            Writer::new(Cursor::new(Vec::new()))
        },
    };
}

Error:

error[E0308]: `match` arms have incompatible types
  --> src\main.rs:13:13
   |
8  |       let writer = match &some_option {
   |  __________________-
9  | |         Some(file_name) => {
10 | |             Writer::new(File::create(file_name).unwrap())
   | |             --------------------------------------------- this is found to be of type `Writer<File>`
11 | |         },
12 | |         _ => {
13 | |             Writer::new(Cursor::new(Vec::new()))
   | |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `File`, found struct `std::io::Cursor`
14 | |         },
15 | |     };
   | |_____- `match` arms have incompatible types
   |
   = note: expected type `Writer<File>`
            found struct `Writer<std::io::Cursor<Vec<u8>>>`

For more information about this error, try `rustc --explain E0308`.

Solution

  • Both File and Cursor implement std::io::Write, so you can solve this by boxing those inner values, giving yourself a Writer<Box<Write>>:

    let writer: Writer<Box<dyn std::io::Write>> = Writer::new(match &some_option {
        Some(file_name) => {
            Box::new(File::create(file_name).unwrap())
        },
        _ => {
            Box::new(Cursor::new(Vec::new()))
       },
    });
    

    Note that this requires a heap allocation for the File/Cursor value. Alternatively, you can use the either crate and its primary type Either instead:

    let writer = Writer::new(match &some_option {
        Some(file_name) => {
            Either::Left(File::create(file_name).unwrap())
        },
        _ => {
            Either::Right(Cursor::new(Vec::new()))
        },
    });
    

    This approach doesn't require any additional heap allocation or indirection. This works because Either implements Write when both the Left and Right variants do.