2022/09/24
はじめに
Rust の勉強を目的とし,Matrix クラスを作る.
それを通して得た知見を残す.
コード全体はこちら.
入門コース目次
- 入門 0 概要
- 入門 1 Struct とメソッド
- 入門 2 Unit Test
- 入門 3 演算子オーバーロードによるインデックスアクセス
- 入門 4 インスタンス生成用の自作マクロ
- 入門 5 module
- 入門 6 自作 Module を利用する
このページで作るもの
演算子オーバーロードを用いて Matrix クラスの要素へのアクセスを便利にする.
Rust における演算子オーバーロード
Rust では,演算子のために用意されたstd::ops
内の Trait を Struct に実装することで演算子オーバーロードが可能である.
例えば+
のためにはstd::ops::Add
を,+=
のためにはstd::ops::AddAssign
を実装すると良い.
本ページでは,インデックスアクセスであるため,不変のIndex
と,可変のIndexMut
を実装する.
コード全体
推奨されている方法というわけではないが,src/matrix/matrix.rs
が肥大化するのを防ぐ目的で,別ファイルに分割した.
Index
をsrc/matrix/index.rs
に,IndexMut
をsrc/matrix/index_mut.rs
に実装した.各テストについても同一フィアルに記載することにした.
use std::ops::Index;
use super::Matrix;
impl<T> Index<u8> for Matrix<T> {
type Output = [T];
fn index(&self, index: u8) -> &Self::Output {
let pos = (self.width as usize) * (index as usize);
&self.data[pos..(pos + self.width as usize)]
}
}
#[cfg(test)]
mod tests {
use crate::matrix;
#[test]
fn get_row() {
let mat = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
assert_eq!(mat[0], vec![0, 1, 2]);
assert_eq!(mat[1], vec![3, 4, 5]);
assert_eq!(mat[2], vec![6, 7, 8]);
assert_eq!(mat, matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]]);
}
}
use std::ops::IndexMut;
use super::Matrix;
impl<T> IndexMut<u8> for Matrix<T> {
fn index_mut(&mut self, index: u8) -> &mut Self::Output {
let pos = (self.width as usize) * (index as usize);
&mut self.data[pos..(pos + self.width as usize)]
}
}
#[cfg(test)]
mod tests {
use crate::matrix;
#[test]
fn get_row() {
let mut mat = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
mat[0][0] = 1;
println!("{:?}", mat);
assert_eq!(mat[0], vec![1, 1, 2]);
assert_eq!(mat, matrix![[1, 1, 2], [3, 4, 5], [6, 7, 8]]);
}
}
解説 ~ Index ~
Matrix は行列でNxM
の要素を持ち,それを一つのVec<T>
として保持する.
一般に,行列の一つの要素を表す際は,n
行m
列のような表現をする.
これをプログラムで表すと,mat[n][m]
のようになっていると自然である.
Index
の実装でこのようなアクセスを可能にしたいので,[m]
でm
個目の要素にアクセスできる共有スライスを返せば良い.
そこで,以下のように実装した.
use std::ops::Index;
use super::Matrix;
impl<T> Index<u8> for Matrix<T> {
type Output = [T];
fn index(&self, index: u8) -> &Self::Output {
let pos = (self.width as usize) * (index as usize);
&self.data[pos..(pos + self.width as usize)]
}
}
#[cfg(test)]
mod tests {
use crate::matrix;
#[test]
fn get_row() {
let mat = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
assert_eq!(mat[0], vec![0, 1, 2]);
assert_eq!(mat[1], vec![3, 4, 5]);
assert_eq!(mat[2], vec![6, 7, 8]);
assert_eq!(mat, matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]]);
}
}
解説 ~ IndexMut ~
IndexMut の実装
Index
への実装と同様の考え方でスライスを返すが,これは可変スライスを返すようにしている.
これにより,mat[0][0] = 0
のような式を書けるようになる.
use std::ops::IndexMut;
use super::Matrix;
impl<T> IndexMut<u8> for Matrix<T> {
fn index_mut(&mut self, index: u8) -> &mut Self::Output {
let pos = (self.width as usize) * (index as usize);
&mut self.data[pos..(pos + self.width as usize)]
}
}
#[cfg(test)]
mod tests {
use crate::matrix;
#[test]
fn get_row() {
let mut mat = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
mat[0][0] = 1;
println!("{:?}", mat);
assert_eq!(mat[0], vec![1, 1, 2]);
assert_eq!(mat, matrix![[1, 1, 2], [3, 4, 5], [6, 7, 8]]);
}
}
おわりに
演算子オーバーロードを実装するために,特別な記法を用いる必要がない点が非常に便利であるし,Trait に対する実装ということで,実装への制限が与えられるのが安心感があって素晴らしいです.