はやし雑記

はやしです

Rustの変数の所有権とかBorrowingについて概ね理解した

基本的にはここに書いてある通り。

Scoping rules - Rust By Example

C/C++エンジニアではないけど、これも参考にさせて頂いた。

C/C++エンジニアのための Rust のデータ所有権とライフタイム入門

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

RAII - Wikipedia

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);
}

pStringのReferenceである&Stringを引数にとる関数である。 v1のReferenceである&v1を引数として渡したり、他の変数に代入したりしている。

Mutable Reference

こちらは変更も可能で、T型の変数は&mut Tで借りることができる。

以下のコードでは、who_are_youPersonのReferenceを、happy_birthday_to_youPersonのMutable Referenceを変数に取っている。 hayashiはImmutableな変数なので、happy_birthday_to_youhayashi&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は変わることはない。