基本的にはここに書いてある通り。
Scoping rules - Rust By Example
C/C++エンジニアではないけど、これも参考にさせて頂いた。
C/C++エンジニアのための Rust のデータ所有権とライフタイム入門
- Ownership (所有権)
- RAII: Resource Acquisition Is Initialization
- Immutable (変更不可能)・Mutable (変更可能)な変数
- Borrowing (借用)
- DataのLifetimeより長い変数にReferenceは渡せない
- Copy trait
Ownership (所有権)
DataのOwnershipを持てる変数は1つだけ
代入や関数の引数として渡すことで、OwnershipはMove (移動)する。
Ownershipを他の変数に与えた元の変数は空の状態になるのでDataの参照はできない。 よって、以下の2つのコードはコンパイルエラーとなる。
fn main() { let v1 = String::from("str"); let v2 = v1; println!("{}", v1); // <- compile error }
fn p(v: String) { println!("{}", v) } fn main() { let v1 = String::from("str"); p(v1); println!("{}", v1); // <- compile error }
しかし、以下のようにi32
のリソースであればコンパイルエラーは起きず、実行できる。
これについてはCopy traitの項で説明する。
fn main() { let v1 = 1; let v2 = v1; println!("{}", v1); }
RAII: Resource Acquisition Is Initialization
Rustでは、リソースの確保は初期化時に行われる。
DataのOwnershipを持つ変数がスコープから外れると、そのときにデータは開放される。
以下のコードでは、main
の中にブロック{}
を作り、そこで"str2"のOwnershipをv2
に渡している。
しかし、v2
のスコープはすぐ外れるので、その外でv2
を参照しようとしてもcannot find value v2
in this scopeと叱られる。
fn main() { let v1 = String::from("str1"); { let v2 = String::from("str2"); println!("{}", v2); } println!("{}", v2); // <- compile error println!("{}", v1); }
Immutable (変更不可能)・Mutable (変更可能)な変数
Immutableな変数はlet
で宣言され、Mutableな変数はlet mut
で宣言される。
以下のコードでは、Immutableなv1
は変更できない。しかし、v1
の持つOwnershipをMutableなv2
へと移動することでDataの変更が可能になる。
fn main() { let v1 = 1; v1 = 2; // <- compile error let mut v2 = v1; v2 = 2; }
Borrowing (借用)
上述のように、代入等の操作によってOwnershipは移動してしまう。 流石にこれではやり辛い。
そこで、他の変数にOwnershipを移動するのではなく、Borrowingすることで対応する。
あげるのは嫌だから貸してあげる、という感じである。
Borrowingでは、他の変数にDataを参照するだけのReferenceと、変更も可能なMutable referenceを渡すことができる。
Reference
読み取り専用のReferenceは複数の変数に渡すことができる。
T
型の変数v
のReferenceは&T
型の&v
である。
以下のコードは、Ownershipの項にあったものを少し変えたものである。 前回とは異なりコンパイルエラーは起こらない。
fn p(v: &String) { println!("{}", v) } fn main() { let v1 = String::from("str"); p(&v1); let v2 = &v1; println!("{}", v1); }
p
はString
のReferenceである&String
を引数にとる関数である。
v1
のReferenceである&v1
を引数として渡したり、他の変数に代入したりしている。
Mutable Reference
こちらは変更も可能で、T
型の変数は&mut T
で借りることができる。
以下のコードでは、who_are_you
はPerson
のReferenceを、happy_birthday_to_you
はPerson
のMutable Referenceを変数に取っている。
hayashi
はImmutableな変数なので、happy_birthday_to_you
にhayashi
や&mut hayashi
を渡してもコンパイルエラーになる。
Mutable Referenceを渡すためには、元の変数がMutableである必要がある。hayashi
をMutableなm_hayashi
にしたうえで、happy_birthday_to_you
に&mut m_hayashi
が渡せる。
struct Person { name: String, age: u32 } fn who_are_you(person: &Person) { println!("I am {}, {} years old", person.name, person.age); } fn happy_birthday_to_you(person: &mut Person) { person.age += 1; } fn main() { let hayashi = Person { name: String::from("Hayashi"), age: 23 }; who_are_you(&hayashi); happy_birthday_to_you(hayashi); // <- compile error happy_birthday_to_you(&mut hayashi); // <- compile error let mut m_hayashi = hayashi; happy_birthday_to_you(&mut m_hayashi); who_are_you(&hayashi); // <- compile error who_are_you(&m_hayashi);
また、BorrowingにおいてはReferenceかMutable Referenceの両方は同時には存在できません。
fn main() { let mut hayashi = Person { name: String::from("Hayashi"), age: 23 }; who_are_you(&hayashi); let rental_hayashi = &hayashi; happy_birthday_to_you(&mut hayashi); // <- compile error happy_birthday_to_you(&mut rental_hayashi); // <- compile error who_are_you(rental_hayashi); }
一度hayashi
のReferenceであるrental_hayashi
を作ってしまうと、Mutable Referenceは作れません。
fn main() { let mut hayashi = Person { name: String::from("Hayashi"), age: 23 }; who_are_you(&hayashi); let mut rental_hayashi = &mut hayashi; happy_birthday_to_you(&mut rental_hayashi); who_are_you(rental_hayashi); }
DataのLifetimeより長い変数にReferenceは渡せない
以下のコードはちゃんとコンパイルできる。
v2
のLifetimeはブロックの終端まで(たった2行の命)だが、v1に所有権を渡すことでブロックの外でも"str"は生きている。
fn main() { let v1; { let v2 = String::from("str"); v1 = v2; } println!("{}", v1); }
対して、以下のコードはコンパイルできません。
fn main() { let v1; { let v2 = String::from("str"); v1 = &v2; // <- compile error } println!("{}", v1); }
これは、v2
に入っているdataのReferenceをよりLifetimeの長いv1
に渡そうとしているから。
fn main() { let v2; let v1; { v2 = String::from("str"); v1 = &v2; // <- compile error } println!("{}", v1); }
v2
のほうが先に死ぬようにしておけば大丈夫。
同じブロック内では先に宣言したほうが先に死ぬので、v1
を先に宣言するとコンパイルエラーになる。
それにしてもlet v1
みたいな宣言をしてちゃんと型推論してくれるのすごい
Copy trait
最初のほうで出てきた、所有権ごと渡している(ように見える)のにエラーになっていないコードは、Copy traitによるもの。 だいたいのprimitive型はこれを持っている。
#[derive(Copy, Clone)]
を頭につければ自動でCopyされるようにしてくれる。
ただし、全てのfieldがCopyできる必要があるので、以下のコードではname
の型を&'static str
にしている。
#[derive(Copy, Clone)] struct Person { name: &'static str, age: u32 } fn who_are_you(person: Person) { println!("I am {}, {} years old", person.name, person.age); } fn happy_birthday_to_you(mut person: Person) { person.age += 1; } fn main() { let hayashi = Person { name: "Hayashi", age: 23 }; who_are_you(hayashi); happy_birthday_to_you(hayashi); who_are_you(hayashi); let mut copy_hayashi = hayashi; copy_hayashi.name = "Ryosuke"; who_are_you(copy_hayashi); who_are_you(hayashi); }
hayashi
は代入のときも関数に渡されるときもコピーされるので、ここではhappy_birthday_to_you
されてもage
は変わることはない。