Compiler Error about Type Mismatch

Hi,

I think the OutputChannel is implementing the ConsoleTrait. But the compiler prompt the error about the type mismatch. What is the reason?

The code is in the playground.

Thanks!

When thinking about generics in Rust it is necessary to always remember that the caller chooses the concrete type that the generic parameter should be, not the callee. If I—the caller—would want a Concole<MyWriter> and I would call Console::<MyWriter>::new(), I wouldn't get a Console<MyWriter> but a Console<OutputChannel>. This violates the contract of the generic parameter. Instead of letting me choose what type Writer should be, just implement Console::new to always return a Console<OutputChannel>:

impl Console<OutputChannel> {
    pub fn new(
        content: String,
        print: bool,
    ) -> Self {
        if !print {
            Self {
                content,
                writer: OutputChannel::from(Box::new(empty())),
            }
        } else {
            Self {
                content,
                writer: OutputChannel::from(Box::new(stdout())),
            }
        }
    }
}

Playground.

2 Likes

jofas, Thanks!

I got it!

In my code, for every trait ConsoleTrait in Console, I assign OutputChannel to it which violates the datatype of writer in the Console.

1 Like

Do you think it is necessarily to have Box<dyn Write + Send> in this case?

Is it enum much better? At least the performance is better.

I think trait object is useful in some struct which has a collection of trait objects. And we need to iterate to run function for each trait object.

Performance of trait object vs. enum is probably negligible in your context as I assume IO will dwarf the overhead of virtual dispatch.

I can't say for certain what the best abstraction in your case will look like, but my general advice would be to make your code as simple as possible and only add extensibility with generics or trait objects as needed. So if you only ever want a Console to work with OutputChannel, remove the Writer parameter from it. If your OutputChannel only ever works with stdout and empty (and maybe a file or something in the future), make it an enum and only revert to using trait objects when your current interface doesn't support your needs anymore.

1 Like

Note that if you do this and find a need for a trait object in future, you can add a variant to the enum which contains the boxed trait object. This should minimize the amount of refactoring that is necessary, and there is no comparable option for adding an enum to a trait object-based API.

3 Likes

Suppose I create an enum like this.

pub enum Chan {
    Stdout(Stdout),
    Stderr(Stderr),
    Empty(Empty),
}

For each trait method, are there only match arms and if let to handle them?

If you need access to the fields of the variants then yes, pattern matching is the way to access them.

Will too many enum elements cause performance issues?

Pattern matching really shouldn't be your concern when thinking about performance. Like I said above, when you deal with IO, pattern matching or virtual dispatch will most likely not be noticeable. If you are concerned about performance, you should benchmark and profile your program under real-world circumstances and optimise accordingly.