Ловушка: слишком быстрое обращение к dyn Trait
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
use std::any::Any;
pub trait AddDyn: Any {
fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn>;
}
impl AddDyn for i32 {
fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn> {
if let Some(downcast) = (rhs as &dyn Any).downcast_ref::<Self>() {
Box::new(self + downcast)
} else {
Box::new(*self)
}
}
}
fn main() {
let i: &dyn AddDyn = &42;
let j: &dyn AddDyn = &64;
let k: Box<dyn AddDyn> = i.add_dyn(j);
dbg!((k.as_ref() as &dyn Any).is::<i32>());
dbg!((k.as_ref() as &dyn Any).downcast_ref::<i32>());
}
-
Тем, кто пришёл из ООП, понятно желание как можно раньше взяться за этот инструмент динамической диспетчеризации.
-
Это не предпочтительный способ решения задач: объекты трейтов ставят нас в ситуацию, когда мы жертвуем знанием о типе, которым располагают и разработчик, и компилятор, ради гибкости.
-
Пример выше доводит ситуацию до абсурда: если бы сложение чисел было завязано на динамическую диспетчеризацию, сделать что-либо было бы трудно.
Но во многих языках программирования динамическая диспетчеризация скрыта, а здесь она более явная.
В реализации
AddDynдляi32сначала нужно попытаться привести аргументrhsк тому же типу, что иi32; если это не так, операция молча завершается неудачей.Затем нужно выделить новое значение в куче, потому что если мы остаёмся в мире динамической диспетчеризации, то это необходимо.
Сложив два значения, чтобы их вывести, мы должны снова привести их (downcast) к «настоящему» типу, который можно вывести, учитывая ограничения трейтов, связанные с операцией до этого момента.
-
Спросите класс: почему нельзя просто добавить ограничения Display в
main, чтобы выводить значения как есть?Ответ: потому что
add_dynвозвращает толькоdyn AddDyn, и мы теряем информацию о том, что реализует тип, между типом аргументов и типом результата. Даже если входные значения реализуютDisplay, тип результата — нет. -
Это приводит к менее производительному коду, который труднее понять.