Rust の「impl Trait」というのは結局はジェネリクスと同じということなんかな。使ってる型が増えたら、それだけ展開されるコードも増える。
Goになじんだ勢としては、それなら動的でいいじゃんということで Box<dyn...> で甘んじたくなってしまうが、呼び出す時 Box::new() が要るのが、ちょっといやん
@zetamatta generics はあくまで T のような多相型のことで、trait の機能は型 T についての制約や集合の性質を記述・実装して、型の世界でプログラミングするようなものだという認識です
@zetamatta つまり、単に
fn mul(a: T, b: T) -> T みたいなものを書いてしまうと、T が積を持たない性質の集合であってもコンパイルできてしまうところで、スカラー乗をもつ空間の要素ですよ、とか、ベクトル積をもつ空間の要素ですよ、とか、自然数ですよ、とか、制約を持たせたいために使う認識です。Bjarne Stroustrup の OOP 的には抽象クラスやインターフェースのほうが近い気はしますが、継承のような古くて、ダイヤモンド継承などの問題を引き起すシステムより洗練された形で同じようなことができるはず
@orumin
あぁ、すみません。trait が interface 的な「必要なメソッドをこの型は装備している」という要件を示したものであるということは理解しているつもりでした。
「Generics」という言葉を持ち出したのは、ちょっと別の意図があって…
@zetamatta 静的に関数型ちっくにやるか動的ディスパッチで OOP ちっくにやるかは多分に好みの問題もあると思いますが、明確なトレードオオフもあって公式書籍のどこかにあったと思います。 https://doc.rust-lang.org/book/ch17-02-trait-objects.html ここらへんかな……
@zetamatta Rustacean は静的にやるほうを好む人が多い印象はありますが
@zetamatta 静的ディスパッチの場合はコンパイル時に解析された、実際に特殊化される可能性のある型のぶんだけコードが生成されますが、そのぶん実行時にもつオブジェクトのサイズが最適化されるはずで、逆に動的ディスパッチはコードはある程度共通な気がしますが、そのぶんファットポインタのオブジェクトが引き回されるので、ストレージ上のサイズは小さくできても実行時のスタックの使い方やメモリフットプリントで差異がありそうです
@orumin
ありがとうございます。
どうもメモリ少ない時代の貧乏性を今まで引きずっていて、実行コードの量が型ごとに増えることにやや抵抗があったんですが、動的な方でもオブジェクト(データ)の方のサイズが増えると考えれば、どっちにしてもどっちかがファットになってしまうということですね。
Rust界では静的を好む人が多いということであれば、郷に従えという言葉もありますし、あまり敬遠したものでもない感じがしました
(というか、Windows では Rust の実行ファイルのサイズは Go の 1/10 くらいで済んでしまうことを考えると、コードサイズなんぞそもそも気にする必要性がないかも)
@zetamatta Go の場合はふつう libc まかせにするところやプラットフォームごとの システムコール呼び出しも全部自前で持っちゃってるからどうしてもバイナリサイズは大きくなりがちなんですよねぇ(そのぶん、どの Linux distro でもバイナリを一個を sftp でも rsync でもいいので投げ込めばどこでも動くのには感心
@orumin
Windows からでも普通に Linux バイナリを(対象ディストリビューションとかを指定することなく)クロスコンパイルできてしまいますものねぇ。長所と短所は表裏一体ですね。。
@zetamatta そういえば、Rust でも embedded target の document なら static dispatch と dynamic dispatch のコードサイズについて論じていないか気になったのですがそれはパッと見つからなかった……のですがそれはそれとしていくつかの有用な hack な掲載されておりました
https://docs.rust-embedded.org/book/unsorted/speed-vs-size.html