はじめに
この記事は「限界開発鯖アドベントカレンダー」23日目の記事です。
いやー、明日はクリスマスイブ、明後日はクリスマスとはなんとも信じがたいです。2020年は過ぎるのがとても早かった。。。
この記事では、stable rustでもうすぐ使えるようになるかもしれないconst genericsと呼ばれる機能について、簡単に解説したいと思います。
なおこの記事でお話しするのは min_const_generics
feature の範囲です。const generics
はとても大きな機能なので、分割されています。今回安定化されるかもしれないのは最低限の部分です。
TL;DR
- constな限定された一部の型の値をgenericsとして持たせることができるようになります。
- rust v1.50-stableでstabilizeされるかもしれません: PR
- すでにnightlyでは使用可能です。
#![feature(min_const_generics)]
対象読者
- RustのGenericsについて理解している方
- Rustのconstについて理解している方
本題
2x3の行列を表すstructを考えてみましょう。普通に書いてみます。
struct Matrix2x3 {
matrix: [[f64; 3]; 2],
}
これを拡張し、2x3ではなく、任意のサイズの行列を定義してみましょう。(サイズは固定とします)
struct Matrix {
// ここでmatrixの長さを指定する方法がない!
matrix: [[f64; ???]; ???],
}
impl Matrix {
fn new_zeroed(cols: usize, rows: usize) -> Self {
Self {
// 配列の長さの指定には定数を使う必要がある!(変数は使えない!)
matrix: [[0.0; rows]; cols],
}
}
}
このコードはコンパイルが通りません!今まではVecなどを使ってヒープアロケーションするしか方法がありませんでした。
const genericsを使うとこれがとても簡単に実現します。
struct Matrix<const COLS: usize, const ROWS: usize> {
matrix: [[f64; ROWS]; COLS],
}
impl<const COLS: usize, const ROWS: usize> Matrix<COLS, ROWS> {
fn new_zeroed() -> Self {
Self {
matrix: [[0.0; ROWS]; COLS],
}
}
}
このように、定数をジェネリクスに持たせることが出来るようになります!
使うときはこのように書けます。
fn main() {
let matrix = Matrix::<2, 3>::new_zeroed();
//もしくは
let matrix: Matrix<2, 3> = Matrix::new_zeroed();
}
本当にジェネリクスの一部となっているのがお分かりいただけたと思います。
implでの応用例を行列の掛け算の実装で見てみましょう。
行列の掛け算は、a × b をするとき、aの列数とbの行数が等しくないと定義されません。
これを今までのRustでは表現できませんでしたが、const genericsを使うと型レベルで表現できます。
impl<
const COLS: usize,
const ROWS: usize,
const K: usize,
> std::ops::Mul<Matrix<K, ROWS>> for Matrix<COLS, K> {
type Output = Matrix<COLS, ROWS>;
fn mul(self, rhs: Matrix<K, ROWS>) -> Self::Output {
// ...
}
}
これにより、a × b をするとき、aの列数とbの行数が一致しないときはコンパイルが通らないようになります!
fn test() {
let matrix = Matrix {
matrix: [
[1, 2, -5, 2],
[3, -1, 7, 3],
[1, 0, 4, 4],
],
};
let _ = matrix * matrix;
}
このコードはコンパイル時にこんなエラーを吐きます。
error[E0308]: mismatched types
--> src/lib.rs:61:22
|
61 | let _ = matrix * matrix;
| ^^^^^^ expected `4_usize`, found `3_usize`
|
= note: expected struct `Matrix<4_usize, {_: usize}>`
found struct `Matrix<3_usize, 4_usize>`
error: aborting due to previous error
今までは実行時にエラーハンドリングをしなければならなかったのを、コンパイル時に解決できます。素晴らしいですね!
おわりに
おそらくこれからどんどんconst generics
の機能が実装されていくと思います。楽しみですね!
明日の限界開発鯖アドベントカレンダーは、くもことなおこさんの担当です。お楽しみに!