冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

Rustの勉強(その12)

この辺は概要覚えててもハマって泣きながら体で覚えた方がわかりやすそうな気がしてます。というわけで前回の続き。

mao-instantlife.hatenablog.com

Borrowingによる問題を防ぐ

イテレータの無効化

こんなことをするとエラーになります。

let mut v = vec![1,2,3];

for i in &v {
println!("{}", i);
v.push(34);
}

上記エラーは、 &v がデフォルトでimmutableだから起きます。まあ、ループで変更せんほうがいいよ、ということらしいです。

使ったら解放する

let y : &i32;

{
let x = 5;
y = &x;
}

println!("{}", y);

let y : &i32;
let x = 5;
y = &x;

println!("{}", y);

もエラーになります。

y の参照が生きている時間が x よりも長くなるのが原因ですね。この辺りをコンパイルエラーとして出してくれるのはちょっと嬉しいです。初めは戸惑うかもしれないけど、慣れてくると実行時エラーが減りそうな気がしています。

Lifetimes

オーナーシップに関する特徴の最後がこのlifetimeです。

所有権の貸し出しはやっぱり複雑になります。リソースの参照がどこから来ているかプログラマが把握できなければメモリリークするとか。。。それを解決するために、Rustのオーナーシップシステムは lifetimes と呼ばれるコンセプトに沿って実行されています。

関数においてはどうか

関数は暗黙的か明示的かの違いはあれど、Lifetimesの指定をして実行されています。

// 暗黙的
fn foo(x: &i32) {
}

// 明示的
fn bar<'a>(y: &'a i32){
}

'athe lifetime a と読むらしいです。下手に日本語に訳すと大仰な言葉になりそうなのでそのまま lifetime と使うのが良さそうですね。技術的にはすべての参照はlifetimeが連携されているけど、コンパイラはよく使うケースを省略可能にしています(後述)。だから大体は暗黙的になっています。

mutableな引数の場合は、

fn huga<'a>(x: &'a mut i32) {
}

で、これは &mut i32 と同じですが、そもそも引数を変更前提で使うこと自体が危ない気がします。

構造体においてはどうか

明示的に書いた場合です。

struct Foo<'a> {
x: &'a i32,
}

fn main() {
let y = &5; // let _y = 5; let y = &_y;と同じ
let f = Foo{ x:y };

println!("{}", f.x);
}

1行目、なんでBorrowingする必要があるんだろう。。。と思ってましたが、構造体の値がBorrowingされたものを期待値として持っているからですね。

実装ブロック(implブロック)

同じく明示的に書いた場合。

struct Foo<'a> {
x: &'a i32,
}

impl <'a> Foo<'a> {
fn x(&self) -> &'a i32 { self.x }
}

fn main() {
let y = &5; // let _y = 5; let y = &_y;と同じ
let f = Foo{ x:y };

println!("{}", f.x());
}

ってか、構造体(型?)と実装についても省略可能なことはチュートリアルで触れただけなので微妙に混乱しておりますが。構造体はフィールドの定義で、メソッドや関数は実装ブロックでの定義?

複数のLifetime

もちろんできます。

fn x_or_y<'a, 'b>(x: &'a str, y: &'b str) -> &'a str

スコープについて

まんま一つ章立てされていますね。

Lifetimes

こちらの注釈がわかりやすいです。特に三番目とかやってしまいそうです。副作用のない関数を細かく作って使う、という書き方を心がけておけば大まかには防げそうな気がしています。

Lifetime Elision

で、上記までのlifetimeの記述は省略することができます。ルールは以下三つです。

  • 関数の引数で省略されたlifetimeは、それぞれ異なるlifetimeパラメータとなる
  • もし、一つのinput lifetimeが確実にあるのであれば、省略のいかんにかかわらず、関数の戻り値の省略されたlifetime全てに適用される
  • もし、複数のinput lifetimeがあって、そのうちの一つが &self&mut self だった場合は、 self のlifetimeは省略されたoutput lifetime全てに適用される

具体的なサンプルはこちら。

Lifetimes

サンプルを見て勘違いに気づきました。参照じゃなければlifetimeは必要ないんですね。あと、今までのサンプルで関数に &self を入れていた理由がちょっとわかりました。lifetimeを省略して書くためにやってるんですね。

ちょっと他に試したいツールがあるので

以下次号なんですが、先にそっちを試そうかと。