この記事は「OPTIMIND x Acompany Advent Calendar 2021」 6日目の記事です。
こんにちは。オプティマインドの菊池です。
Rustに関する小ネタとして、num クレートについて書きたいと思います。
ジェネリクスを用いたadd関数
皆様は、業務中や趣味のプログラミング、あるいはトイレ、夢の中などで、関数を書くことがしばしばあると思います。
そうした関数漬けの日々を送るあなたは、あるRustによる開発プロジェクトの中で以下のような革命的な関数を世に産み落としました。
fn add(a: u128, b: u128) -> u128 {
a + b
}
この関数はあまりにも革命的なので、プロジェクトコードのあらゆる箇所で使われるようになったのですが、ある日、ちょっと困ったことが起きました。このadd関数を、usize
型の整数に対しても使いたいという要望が出てきたのです。
最も簡単な解決策は、下記のようにusize用の別の関数をもう一つ作ってしまうことです。
fn add_usize(a: usize, b: usize) -> usize {
a + b
}
これで足元の課題は解決しましたが、今後u64
でも使いたい、i128
でも使いたいなどといった要望が出てくるたびに関数を作っていたのではとても面倒です。できれば、何度も実質的に同じ振る舞いを記述せずに済ませたいところです。
幸い、Rustはジェネリクスの機能を持っているため、std::ops::Add
トレイトを実装した型というジェネリック境界により、以下のように汎用的に記述することができます。
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
これで、std::ops::Add
を実装している型であれば、どんな型でもadd関数を使うことができます。
ceil関数の場合は?
同じことを、以下のceil関数でも試してみたいと思います。(これはa/bの整数切り上げを計算する関数です。こちらなどを参照。)
fn ceil(a: u128, b: u128) -> u128 {
(a + b - 1) / b
}
この関数の中では、加算、減算、除算が使われているので、各演算に対応したトレイトstd::ops::Add
、std::ops::Sub
、std::ops::Div
と、2度使っているb
のためにCopy
トレイトを実装してやればよさそうです。
それを実装したのが以下です。
fn ceil<
T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Div<Output = T>,
>(
a: T,
b: T,
) -> T {
(a + b - 1) / b
}
しかし、このコードをコンパイルしてみると、以下のようなエラーが起こってしまいました。
| (a + b - 1) / b
| _______^ expected type parameterT
, found integer
|
= note: expected type parameterT
found type{integer}
コード中に登場する整数1
について、コード中に具体的な型を特定する手段がなく何らかの整数型としか類推できないため、コンパイルエラーとなってしまいます。このように、処理の中に実際の数値が登場する場合は、addと同じ方法では記述できません。これは困りました。
numクレートでceil関数を実装
このようなケースで役に立つのが、numクレートです。numクレートはcargo.tomlに以下のように記述することで利用することができます。
[dependencies]
num="0.4.0"
このクレートを使うと、先ほどの1
の部分はT::one()
と書くことができ、ceil関数は以下のようになります。
fn ceil<T: Copy + num::Integer>(a: T, b: T) -> T {
(a + b - T::one()) / b
}
1
の部分だけでなく、トレイト境界の記述も非常にスッキリしています。numクレートはこのように、数値の計算に関して直感的に利用しやすいトレイトを提供しています。
numクレートに実装されている値
numクレートには数値として、Zero
トレイトと、One
トレイトが用意されています。それぞれ::zero()``::one()
で、各型の0、1の値を呼び出すことができ、これらはnum::Num
やnum::Integer
などに実装されています。
fn do_something<T: num::Num>() {
let zero = T::zero();
let one = T::one();
}
2、3などの数字を扱いたい場合も、以下のようにして実現することができます。
fn do_something<T: Copy + num::Num>() {
let one: T = T::one();
let two: T = one + one;
let three: T = one + two;
}
numクレートに含まれている便利なトレイト・構造体
最後に、numクレートに含まれているトレイト・構造体で、便利なものをまとめておきます。
num::Num
整数型、浮動小数点数型を含む数値全般を扱うトレイト。加減乗除と剰余の演算、PartialEq、Zero、Oneが実装されている。
num:Integer
整数型を扱うトレイト。num:Numの実装されているトレイトに加え、Eq、Ordなどが実装される。
is_even
、is_odd
、gcd
、lcm
、extended_gcd
などのちょっとしたメソッドも実装してくれる。
num::Float
浮動小数点数型を扱うトレイト。num:Numの実装されているトレイトに加え、Negなどが実装される。
num::BigInt
巨大な整数型を扱うことができる型。
let mut x: BigInt = 1.into();
for i in 2..50 {
x *= i;
}
println!("{:?}", x); // 608281864034267560872252163321295376887552831379210240000000000
num::Complex
複素数を扱うことができる型。
let mut x: Complex<i128> = Complex::new(10, 10);
let mut y: Complex<i128> = Complex::new(2, 5);
println!("{}", (x * y)); // -30+70i
println!("{}", (x * y).conj()); // -30-70i
最後に宣伝
株式会社オプティマインドでは、一緒に働く仲間を大募集中です。
カジュアル面談も大歓迎ですので、気軽にお声がけください。
【エンジニア領域の募集職種】
●ソフトウェアエンジニア
●QAエンジニア
●Androidアプリエンジニア
●組合せ最適化アルゴリズムエンジニア
●経路探索アルゴリズムエンジニア
●バックエンドエンジニア
●インフラエンジニア
●UXUIデザイナー
【ビジネス領域の募集職種】
●セールスコンサルタント
●採用・人事