Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

traitのaliasを作る ~ associated typeを添えて

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 はあるがその機能はまだ使えない。

github.com

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

人間の気分的にはこれで終わっているのだが、残念ながら u32i64 もこの 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>::OutputDisplay 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>
{
}

方向性はあっているのだが、この機能は以下で議論中であり、まだ利用できない。

github.com

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 を使えば四則演算をいちいち細かく指定する必要はない。

https://crates.io/crates/num

いやぁ、 trait って本当に素晴らしいもんですね。