Rust には整数の primitive 型が多数あるが、どの型でもよしなに計算してくれる関数を書きたくなる。四則演算子を表す trait はあるのだが、細かく分かれているので指定するのが大変である。
use std::fmt::Display; use std::ops::{AddAssign, Mul, MulAssign}; fn execute<T>(mut x: T, y: T) where T: Display + Copy + MulAssign + Mul<Output = T> + AddAssign, { println!("x={}, y={}", x, y); x *= y; println!("x * y = {}", x); x += y * y; println!("x * y + y ^ 2 = {}", x); } fn main() { execute(3u32, 5u32); execute(4i64, -3i64); }
せめてこれらの trait をまとめてエリアスにできれば楽になる。しかし、 issue はあるがその機能はまだ使えない。
work around は紹介されているので、こちらを使うことになる。
まず、 supertrait を指定した新たな trait を作る。
trait LooksLikeNumber: Display + Copy + MulAssign + Mul<Output = Self> + AddAssign {}
この LooksLikeNumber
trait を使えば、 where
を使うことなくすっきりと書ける。また、同様に「数値」を引数に取る関数や型が増えたときにも、 LooksLikeNumber
と書くだけで済む。
fn execute<T: LooksLikeNumber>(mut x: T, y: T) { ..snip.. }
人間の気分的にはこれで終わっているのだが、残念ながら u32
も i64
もこの trait を実装してないのでエラーとなる。
| 17 | execute(3u32, 5u32); | ^^^^^^^ the trait `LooksLikeNumber` is not implemented for `u32` |
もちろん、自分で実装を書けば良いが、こんなことをしていると日が暮れてしまう。
impl LooksLikeNumber for u32 {} impl LooksLikeNumber for i64 {}
そこで、 supertrait をすべて実装している型は自動的に trait を実装できるように、定義を書く。
impl<T> LooksLikeNumber for T where T: Display + Copy + MulAssign + Mul<Output = T> + AddAssign {}
これで、 u32
i64
だけではなく、必要な四則演算が定義されているすべての型について execute
メソッドが利用可能となった。
execute(1.5f64, 2.2f64); execute(Complex::new(2i16, 1), Complex::new(-4, 6));
浮動小数点数の演算で誤差が出ているのはご愛嬌。
x=1.5, y=2.2 x * y = 3.3000000000000003 x * y + y ^ 2 = 8.14 x=2+1i, y=-4+6i x * y = -14+8i x * y + y ^ 2 = -34-40i
さて、次は execute
にこんな処理を加える。
println!("y * 4 = {}", y << 2);
<<
は Shl<i32>
という trait で提供されており、出力される型 Shl<i32>::Output
は Display
trait を実装していて欲しい。よって、こう書きたくなる。
trait LooksLikeNumber: Display + Copy + MulAssign + Mul<Output = Self> + AddAssign + Shl<i32> where <Self as Shl<i32>>::Output: Display, { } impl<T> LooksLikeNumber for T where T: Display + Copy + MulAssign + Mul<Output = T> + AddAssign + Shl<i32>, <T as Shl<i32>>::Output: Display, { }
しかし、これはエラーとなる。
| 17 | fn execute<T: LooksLikeNumber>(mut x: T, y: T) { | ^^^^^^^^^^^^^^^ `<T as Shl<i32>>::Output` cannot be formatted with the default formatter |
LooksLikeNumber
を実装している型が、 where
で指定した trait 境界を満たしているとは限らないのである。そうなると、こう書きたくなる。
trait LooksLikeNumber: Display + Copy + MulAssign + Mul<Output = Self> + AddAssign + Shl<i32, Output: Display> { }
方向性はあっているのだが、この機能は以下で議論中であり、まだ利用できない。
error[E0658]: associated type bounds are unstable | 5 | trait LooksLikeNumber: Display + Copy + MulAssign + Mul<Output = Self> + AddAssign + Shl<i32, Output: Display> | ^^^^^^^^^^^^^^^ | = note: see issue #52662 <https://github.com/rust-lang/rust/issues/52662> for more information
issue 内で、 この問題に関するワークアラウンド も紹介されている。 trait 境界を与えることはできないが、具体的な型を与えることはできるので、そうすればいいのだ。 LooksLikeNumber
に関連型 S
を定義する。
trait LooksLikeNumber: Display + Copy + MulAssign + Mul<Output = Self> + AddAssign + Shl<i32, Output = Self::S> { type S: Display; }
では、関連型 S
はどう実装すればいいのだろうか。都合がいいことに、 Shl<i32>::Output
という Display
trait を実装する型があるので、それを使うと良い。
impl<T> LooksLikeNumber for T where T: Display + Copy + MulAssign + Mul<Output = T> + AddAssign + Shl<i32>, <T as Shl<i32>>::Output: Display, { type S = <T as Shl<i32>>::Output; }
<<
を使うようにしたので、先に追加した浮動小数点数や複素数のコードは動かなくなったが、 u64
i32
のコードは想定通りに動作する。
x=3, y=5 x * y = 15 x * y + y ^ 2 = 40 y * 4 = 20 x=4, y=-3 x * y = -12 x * y + y ^ 2 = -3 y * 4 = -12
ところで、冒頭に、
四則演算子を表す trait はあるのだが、細かく分かれているので指定するのが大変である
とこのエントリを書くためにあえて書いたのだが、以下の PrimInt
trait を使えば四則演算をいちいち細かく指定する必要はない。
いやぁ、 trait って本当に素晴らしいもんですね。