落とし穴: すぐに 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>()); }
-
OOP のバックグラウンドから来ると、この動的ディスパッチという 道具にできるだけ早く頼ろうとするのは理解できます。
-
これは推奨されるやり方ではありません。トレイトオブジェクトを使うと、 開発者とコンパイラの双方が持っている型に関する知識を、柔軟性と 引き換えに手放す状況になります。
-
上の例はこれを極端なところまで押し進めています。数値の加算が 動的ディスパッチのプロセスに縛られていたら、ほとんど何もできなく なってしまうでしょう。
しかし、動的ディスパッチは多くのプログラミング言語ではしばしば 隠されています。ここではそれがより明示的になっています。
AddDynのi32実装では、まずrhs引数をi32と同じ型へ ダウンキャストできるか試す必要があり、そうでない場合は黙って 失敗します。次に、新しい値をヒープに確保する必要があります。これを動的 ディスパッチの世界にとどめておくなら、それが必要になるからです。
2 つの値を足し合わせたあと、それを見たい場合は、ここまでの操作で 課されたトレイト境界のため、そのままでは出力できないので、再び 出力可能な “実際の” 型へダウンキャストしなければなりません。
-
クラスに質問してみましょう: なぜ
mainに Display 境界を追加する だけでは、そのまま表示できないのでしょうか?答え:
add_dynが返すのはdyn AddDynだけなので、引数の型から 戻り値の型に至るまでの間に、その型が何を実装しているかという情報を 失ってしまいます。入力がDisplayを実装していても、戻り値の型は そうではありません。 -
この結果、パフォーマンスが低く、理解もしにくいコードになります