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 に対する実装ということで,実装への制限が与えられるのが安心感があって素晴らしいです.