Что означает ошибка Rust «вы можете упаковать найденное значение и привести его к объекту типажа»?

Что означает эта ошибка?

Репродукция

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`.

Код:

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),
    };
}
dyn Trait точно так же, как str едва ли может существовать в стеке, потому что оба не имеют размера (не имеют статически известного размера), вам нужна некоторая косвенность, например Box или ссылка.
cafce25 10.06.2024 13:35

Вот на самом деле минимальный воспроизводимый пример пожалуйста, всегда старайтесь максимально сократить пример перед публикацией.

cafce25 10.06.2024 13:39

@ cafce25, если я не понимаю, что происходит, я не могу понять, как это уменьшить. РЖУ НЕ МОГУ

Fred Hors 10.06.2024 13:43
минимальный воспроизводимый пример содержит несколько советов, как можно сократить код, который работает вообще без знания ошибки.
cafce25 10.06.2024 13:46

Если я использую Box<dyn Payable>, я не знаю, как получить от него счет-фактуру: play.rust-lang.org/…

Fred Hors 10.06.2024 13:46

Вы не можете, но и для dyn Payable это не будет ничем отличаться: вам нужно добавить к признаку всю необходимую функциональность.

cafce25 10.06.2024 13:48

О боже мой. Можете ли вы объяснить мне, как решить эту проблему?

Fred Hors 10.06.2024 13:49

Используйте Box<dyn Payable> именно то, что говорит вам компилятор.

cafce25 10.06.2024 13:49

Да, но мне нужен счет от кредитора.

Fred Hors 10.06.2024 13:50

Затем вам нужно добавить в признак всю необходимую функциональность. Например, доступ к нужным вам полям.

cafce25 10.06.2024 13:50

Что ты имеешь в виду? У меня есть много методов, которым нужен счет-фактура. Должен ли я вместо этого создать другие методы с аргументом «оплата»? Это безумие!

Fred Hors 10.06.2024 13:51

Просто укажите struct Invoice { direction: Direction, … }, где Direction — это enum Direction { Out, In }, а затем добавьте к этому свои методы struct.

Jmb 10.06.2024 13:59

Вероятно, вам следует переформулировать свой вопрос, потому что на вопрос, который вы задали в посте, есть ответ, в котором вам предлагается использовать Box<dyn Payable>, но судя по вашим комментариям, ваш реальный вопрос выглядит другим.

cafce25 10.06.2024 14:03

Вместо черты вы можете создать enum Invoice { In(InvoiceIn), Out(InvoiceOut) }. Общие для обоих методы (которые теперь есть в признаке) будут реализованы в перечислении.

user4815162342 10.06.2024 14:07

«Если я использую Box<dyn Payable>, я не знаю, как получить из него Invoice» → Как получить ссылку на конкретный тип из объекта типажа?

cafce25 10.06.2024 14:38
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
15
150
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот попытка показать две возможности, упомянутые в комментариях.

Этот пример разделен на три части:

  • «общая» часть содержит только основные элементы вашей проблемы, без какого-либо решения, позволяющего получить ожидаемую отправку и понижение уровня,
  • «динамическая» часть использует динамическую отправку, которую вы пытались достичь; в основном он заключается в использовании std::any::Any для приведения к исходному типу,
  • часть «enum» делает то же самое более традиционным способом, используя enum.

Теоретически «динамическое» решение может быть открыто для гораздо большего количества вариантов (реализованных где-то еще), чем те, которые присутствуют в «общей» части, но поскольку вам, похоже, нужно какое-то понижение, я не думаю, что это действительно так. соответствует вашим потребностям.

Решение «перечисление» кажется мне лучшим, особенно потому, что вам, похоже, нужно спуститься к конечному числу вариантов (известных раз и навсегда в перечислении). Обратите внимание, что контейнер enum_dispatch может помочь перенаправить вызовы метода через варианты.

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
*/

Другие вопросы по теме