この記事について
Rust初心者である著者がアプリ作成を通じてRustを勉強した事を共有していく記事です。最終的にはルービックキューブのアプリ作成を目指しています。
前回の投稿「Rust + seed でWebAssemblyを体験してみる。」の続きです。
状況
前回の記事では自分が作りたいルービックキューブのアプリの技術的基盤の確認でした。そこから、Canvas要素にワイヤーフレームの立方体を一つ描画するまで実装を進めました。今後は立方体の面を塗りつぶしたり、3×3×3の立方体を描画する実装を進める予定です。ただ、その前にちゃんと使いこなせないといけないと思った事があります。
表題にもある通り、参照渡しに関してです。そうです、「無印」「&」「&mut」の使い分けです。
今後実装を進めていくと、複数種類のStructを使用する事になると思います。そうなると、関数で値を渡すケースが増えてきます。前述の実装をしている時は、コンパイラのエラーにまかせっきりでサジェストされるエラーメッセージ通りに修正していた感じでした(※Rustのコンパイルエラーのサジェストは優秀ですね)。
が、どんな変数にはどの種類の引数定義をすべきかちゃんと把握していないとバグの元ですし、コンパイルエラーメッセージに頼っていると都度コンパイルしなければならないので、実装する速度が遅くなります。これはいけません。
将来的に使うと思う機能、クロージャでもこの件の把握は重要になりそうです。
概要把握
まずは、公式ドキュメント、The Rust Programming Language 日本語版を読んで基本概念を理解していこうと思います。
所有権を理解する
自分の現在の理解では、Rustではメモリ安全性を確保する為に所有権という概念を持ち、それをコントロールする為に「無印」「&」「&mut」が存在すると思っています。
「変数とデータの相互作用法: ムーブ」に所有権が必要な理由が説明されています。ここでの重要ポイントは以下の点になるかと思います。
- 1つのメモリ領域を2つの変数が所有権を確保していると、二重解放エラーの恐れがあるので、一つの変数のみ所有権を確保する。
- 主に整数の様なプリミティブ型ではCopyトレイトを実装しているので、所有権移動は発生しない
この部分は「無印」の変数の振る舞いを説明しています。
Copyトレイト
重要な用語だと思ったので説明を引用します。
RustにはCopyトレイトと呼ばれる特別な注釈があり、整数のようなスタックに保持される型に対して配置することができます。
型がCopyトレイトに適合していれば、代入後も古い変数が使用可能になります。
もし所有権を考えたくない様なStructを作りたい場合にはCopyトレイトを実装してディープコピーをすればよさそうです。とはいえ、基本的にはstring型の様にcloneメソッドを実装して明示的に使い分けた方が良さそうです。
参照と借用
所有権を理解するの最後のセクション「戻り値とスコープ」では、
関数を使う上でのスコープの移り変わりを説明しています。
そしてこの部分で発生している煩雑になってしまう問題をこの「参照と借用」で解消しています。
重要ポイントをピックアップすると以下の様になると思います。
- 「&」を付ける事で、参照型になる。
- 呼び出し側では「
calculate_length(&s1)
」の様に、変数に「&」をつける。 - 関数の引数の型指定には「
s: &String
」の様に型指定部に「&」をつける。 - 参照渡しをする部分以外は無印のものと変わりない。
- あくまでも参照であり、受け取った関数内部では所有権を持たないので、その値を変更する事は出来ない。
可変な参照
前述「参照と借用」の一部分ですが抜き出します。
そしていきなりですが、重要ポイントをピックアップしてみます。
- 使用する変数の宣言には「
let mut s = String::from("hello");
」の様に、「mut」をつける。 - 関数の引数では「
fn change(some_string: &mut String)
」の様に引数データ型に「&mut」をつける。 - 「
let r1 = &mut s;
」の様に可変な参照を使えるのは同時に一つだけ。 - 「
let r1 = &s;
」の様に参照した後に「let r3 = &mut s;
」とする事は出来ない。
最後の2つが「mut」の特性を示していると思います。同じ変数(メモリの中身)を2箇所以上で更新したりする事を許さなかったり、参照している間はその中身を変える事は許さない、という制限をかけてくれます。
当然ながら、「mut」を使うのは値の中身を更新する事が必要な部分だけにするのが望ましいと思います。
関連部分把握
基本概念を把握できたと思いますので、関連部分の詳細を見ていきます。
Structのメソッドの引数で使うselfの扱い
Structのメソッドでは、引数にselfを使用する事が多いと思いますがその形式も調べてみます。基本以下の形で大丈夫そうです。
- Structの中身を変える関数には、「
&mut self
」を使う - Structの中身を参照して何か処理する関数には「
&self
」を使う
関数の返却型
先ほどの「戻り値とスコープ」に加え、「宙に浮いた参照」が理解の助けになりそうです。
関数での戻り値も所有権の受け渡しが発生するという事を意識する必要がありそうです。
結果として以下の重要ポイントが出てくると思います。
- 関数内部で新規生成したStructを返却する時には「無印」を使う。
- スコープ外変数を使用する様なStructのメソッドでは「&」を使わないと所有権が他に移ってしまう。
基本事項まとめ
- プリミティブ型(Copyトレイト保持)と、それ以外のデータ型は振る舞いが異なる。
- Copyトレイトを実装する事も出来るが、別のメモリを参照する事になるので、プリミティブ型的に使うStruct以外では使わない方がよさそう。
- 使い捨ての変数は無印で関数に渡して問題なさそう。とはいえ、参照渡しでないと、呼び出し側の自由度が低くなりそうなので、公開関数の引数は参照渡しでよい気がします。
- 関数の戻り値はどのスコープの変数を返却するのかで扱いを決めなくてはいけない。
自分のアプリでの使用シーン考察
ここまで把握した所で、自分のアプリでの使用する事を考えます。
struct Cube
ルービックキューブの一つのブロック(?)を表すStructです。情報要素として以下の値を保持しています。
今後はこれの3×3×3の集合体としてルービックキューブを表現する予定です。位置は3次元のPoint型を使用する想定です。
- 1辺の長さ
- 中心点の位置
- 8つの頂点の、中心点からの相対位置
- 6つの面の色
- x,y,zそれぞれの軸を中心とした回転角
今の設計では、中心点の位置と、回転角が可変です。相対的頂点位置は物体のモデル情報的イメージです。立方体で固定です。
使用するメソッドと参照種類考察
今後、以下のメソッドを整備していく予定です。その際の引数や戻り値の参照種類を考えてみます。
各軸中心の回転メソッド
引数型:Struct cubeで保持する値を変えるので、「&mut self
」、回転角はプリミティブ型なので無印「f32
」
中心点位置移動メソッド
引数型:Struct cubeで保持する値を変えるので、「&mut self
」、座標情報は参照型「&Point
」で指定。
描画用の頂点位置取得メソッド
引数型:Struct cubeで保持する情報は参照のみなので「&self
」、対象の頂点情報を参照型「&
」で指定する。
返り型:計算した結果を、新しくPoint型を作って返すので無印
描画対象の頂点を取得するメソッド
立方体を描画する時、見える部分か見えない部分か判断して描画する事になる想定です。その描画対象ポイントを取得するメソッドです。
引数型:Struct cubeで保持する情報は参照のみなので「&self
」、対象の頂点情報を参照型「&」で指定する。
返り型:保持するいずれかの頂点を参照返却するので「&Point
」を指定。(新しくPoint型を作って返却でもいいですが)
終わりに
今回把握した事は、社内で行っているRust勉強会でも既に出ていた要素のはずです。しかし当時はやっぱりふわふわ理解だった事を思い知りました。自分が必要に迫られていると雰囲気で理解していた内容があぶりだされます。そしてこの記事を書くために再度読み直しましたが、頭の中への入り具合が全然違いました。基本を把握していないとその上に積み重なる知識も不安定になるのでちゃんと復習してよかったと思いました。
やはり自分は実際に使わないと身につかないタイプですw。
この記事が、Rustを勉強していて3種類の参照渡しにもやもやしている人の理解助けになれば幸いです。