Search code examples
rustcoerciontrait-objects

What does Rust error "you could box the found value and coerce it to the trait object" mean?


What does this error mean?

Reproduction

error[E0308]: mismatched types
  --> src/main.rs:50:35
   |
50 |           PaymentType::InvoiceIn => InvoiceIn {
   |  ___________________________________^
51 | |             name: "Invoice 1".to_string(),
52 | |             payed: false,
53 | |         },
   | |_________^ expected `dyn Payable`, found `InvoiceIn`
   |
   = note: expected trait object `dyn Payable`
                    found struct `InvoiceIn`
   = help: `InvoiceIn` implements `Payable` so you could box the found value and coerce it to the trait object `Box<dyn Payable>`, you will have to change the expected type as well

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

Code:

trait Payable {
    fn toggle_payed(&mut self);
}

enum PaymentType {
    InvoiceOut,
    InvoiceIn,
}

struct Payment {
    r#type: PaymentType,
}

struct InvoiceOut {
    name: String,
    payed: bool,
}

impl Payable for InvoiceOut {
    fn toggle_payed(&mut self) {
        self.payed = !self.payed
    }
}

struct InvoiceIn {
    name: String,
    payed: bool,
}

impl Payable for InvoiceIn {
    fn toggle_payed(&mut self) {
        self.payed = !self.payed
    }
}

fn save_invoice_in(invoice: &InvoiceIn) {
    println!("{}", invoice.name)
}

fn save_invoice_out(invoice: &InvoiceOut) {
    println!("{}", invoice.name)
}

fn main() {
    let payment = Payment {
        r#type: PaymentType::InvoiceOut, // This comes from user!
    };

    let payable: dyn Payable = match payment.r#type {
        PaymentType::InvoiceIn => InvoiceIn {
            name: "Invoice 1".to_string(),
            payed: false,
        },
        PaymentType::InvoiceOut => InvoiceOut {
            name: "Invoice 2".to_string(),
            payed: false,
        },
    };

    // Do something else with payable here

    payable.toggle_payed();

    // Do something else with payable here

    match payment.r#type {
        PaymentType::InvoiceIn => save_invoice_in(&payable),
        PaymentType::InvoiceOut => save_invoice_out(&payable),
    };
}

Solution

  • Here is an attempt to show the two possibilities mentioned in the comments.

    This example is subdivided in three parts:

    • the «common» part contains only the basics elements of your problem, without any solution to get the kind of dispatch and downcasting you expect,
    • a «dynamic» part uses the dynamic dispatch you tried to achieve; it mainly consists in the usage of std::any::Any in order to downcast to the original type,
    • an «enum» part does the same in a more conventional way, relying on an enum.

    In theory, the «dynamic» solution could be open to many more variants (implemented elsewhere) than the ones which are present in the «common» part, but since you seem to need some kind of downcasting, I don't think it really suits your need.

    The «enum» solution looks the best to me, and especially because you seem to need to downcast towards a finite number of variants (known once for all in the enum). Note that the enum_dispatch crate can help forward the method calls through the variants.

    mod common_part {
        pub enum PaymentType {
            InvoiceOut,
            InvoiceIn,
        }
        pub struct Payment {
            pub r#type: PaymentType,
        }
    
        pub struct InvoiceIn {
            pub name: String,
            pub payed: bool,
        }
    
        pub struct InvoiceOut {
            pub name: String,
            pub payed: bool,
        }
    
        pub trait Payable {
            fn toggle_payed(&mut self);
        }
    
        impl Payable for InvoiceIn {
            fn toggle_payed(&mut self) {
                self.payed = !self.payed
            }
        }
    
        impl Payable for InvoiceOut {
            fn toggle_payed(&mut self) {
                self.payed = !self.payed
            }
        }
    
        pub fn save_invoice_in(invoice: &InvoiceIn) {
            println!("save_invoice_in: {}", invoice.name)
        }
    
        pub fn save_invoice_out(invoice: &InvoiceOut) {
            println!("save_invoice_out: {}", invoice.name)
        }
    }
    
    mod dyn_part {
        use super::common_part::*;
    
        pub trait DynPayable: Payable {
            fn as_any(&self) -> &dyn std::any::Any;
        }
    
        impl DynPayable for InvoiceIn {
            fn as_any(&self) -> &dyn std::any::Any {
                self
            }
        }
    
        impl DynPayable for InvoiceOut {
            fn as_any(&self) -> &dyn std::any::Any {
                self
            }
        }
    }
    
    mod enum_part {
        use super::common_part::*;
    
        pub enum Invoice {
            In(InvoiceIn),
            Out(InvoiceOut),
        }
    
        impl Payable for Invoice {
            fn toggle_payed(&mut self) {
                match self {
                    Self::In(invoice) => invoice.toggle_payed(),
                    Self::Out(invoice) => invoice.toggle_payed(),
                }
            }
        }
    }
    
    fn main() {
        use common_part::*;
        let payments = [
            // This comes from user!
            Payment {
                r#type: PaymentType::InvoiceIn,
            },
            Payment {
                r#type: PaymentType::InvoiceOut,
            },
        ];
        {
            use dyn_part::*;
            println!("~~~~ using dynamic dispatch ~~~~");
            for payment in payments.iter() {
                let mut payable: Box<dyn DynPayable> = match payment.r#type {
                    PaymentType::InvoiceIn => Box::new(InvoiceIn {
                        name: "Invoice 1".to_string(),
                        payed: false,
                    }),
                    PaymentType::InvoiceOut => Box::new(InvoiceOut {
                        name: "Invoice 2".to_string(),
                        payed: false,
                    }),
                };
                payable.toggle_payed();
                if let Some(invoice) =
                    payable.as_any().downcast_ref::<InvoiceIn>()
                {
                    save_invoice_in(invoice);
                }
                if let Some(invoice) =
                    payable.as_any().downcast_ref::<InvoiceOut>()
                {
                    save_invoice_out(invoice);
                }
            }
        }
        {
            use enum_part::*;
            println!("~~~~ using an enum ~~~~");
            for payment in payments.iter() {
                let mut payable = match payment.r#type {
                    PaymentType::InvoiceIn => Invoice::In(InvoiceIn {
                        name: "Invoice 1".to_string(),
                        payed: false,
                    }),
                    PaymentType::InvoiceOut => Invoice::Out(InvoiceOut {
                        name: "Invoice 2".to_string(),
                        payed: false,
                    }),
                };
                payable.toggle_payed();
                match &mut payable {
                    Invoice::In(invoice) => save_invoice_in(invoice),
                    Invoice::Out(invoice) => save_invoice_out(invoice),
                }
            }
        }
    }
    /*
    ~~~~ using dynamic dispatch ~~~~
    save_invoice_in: Invoice 1
    save_invoice_out: Invoice 2
    ~~~~ using an enum ~~~~
    save_invoice_in: Invoice 1
    save_invoice_out: Invoice 2
    */