Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 4. Ownership - 2 Copy/Clone/関数

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、Ownershipの概要について学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Ownership

Stack, Heapの概念が重要になってきます。

StackはFILO(First In, Last Out)であり、固定長のデータを保存可能です。例えば、整数やポインタのアドレスなどがStackにあたります。また、アクセスが早いです。

HeapはStringなどのサイズが不定であるものや、mutableで変更する可能性があるデータになります。アクセスは比較的遅いです。

κeen様のコチラの記事がとても勉強になります! keens.github.io

Stack-Only Data: Copy

Stackの中でも、 Stack-Only Data: Copy であるものは、別変数にコピーすることが可能になります。ドキュメントからの引用より、 intergerbooleanfloatchar が可能であり、対象の型で構成された Tuples もコピー可能です。

・All the integer types, such as u32. ・The Boolean type, bool, with values true and false. ・All the floating point types, such as f64. ・The character type, char. ・Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not.

例えば、 i32 の場合、コピー可能であるため、x = 5 として、 y = x としても、yx がコピーされるため、 y = x をした段階でも xy 共にOwnerを失わず、後続処理で使用することが可能です。

fn main() {
    let x: i32 = 5;
    let y: i32 = x;
    println!("x -> {}", x);
    println!("y -> {}", y);
}
x -> 5
y -> 5

String はコピー不可能であるため、代入した段階でOwnerが譲渡されます。下記の例ですと、 let s2: String = s1; により、 s1 のOwnerを s2 に譲渡されるため、後続の println!("s1 -> {}", s1); では使用できなくなります。

fn main() {
    let s1: String = String::from("hello");
    let s2: String = s1;
    println!("s1 -> {}", s1);
    println!("s2 -> {}", s2);
}
error[E0382]: borrow of moved value: `s1`
 --> demo.rs:4:23
  |
2 |     let s1: String = String::from("hello");
  |         -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
3 |     let s2: String = s1;
  |                      -- value moved here
4 |     println!("s1 -> {}", s1);
  |                          ^^ value borrowed here after move

譲渡された s2 が正常動作している点についても記載致します。

fn main() {
    let s1: String = String::from("hello");
    let s2: String = s1;
    println!("s2 -> {}", s2);
}
s2 -> hello

Copy 自体がどこで定義されているのか気になるので辿っていくと、 Trait std::marker::Copy が用意されております。

std::marker::Copy - Rust

そして、 Trait std::marker::Copy を各型への Copy の実体化をしています。

marker.rs.html -- source

Clone

Heapされる変数であっても、Ownerを譲渡せずに双方の変数を利用したい場合もあります。例えば、文字列置換前後の文字列長さ比較があげられます。その時に利用するのが Clone です。

Copy 時の文字列代入式を Clone に書き換えて実装した場合、下記のように問題なく実行可能です。

fn main() {
    let s1: String = String::from("hello");
    let s2: String = s1.clone();
    println!("s1 -> {}", s1);
    println!("s2 -> {}", s2);
}
s1 -> hello
s2 -> hello

CloneCopy と同様にtraitで定義されています。

std::clone::Clone - Rust

intergerbooleanfloatchar はマクロにより、 Clone が定義されています。

clone.rs.html -- source

そのため、下記のように、 Clone させることも可能です。あまりに実用からかけ離れているため、使用はしませんが・・・。

fn main() {
    let x: i32 = 5;
    let y: i32 = x.clone();
    println!("x -> {}", x);
    println!("y -> {}", y);
}
x -> 5
y -> 5

String の場合は、型内部で Clone の実体化がされています。

string.rs.html -- source

細かく見ていくと、StringVec により生成されていることも見受けられます。

string.rs.html -- source

関数

関数の場合は、Ownershipはどのように変化するのでしょうか。

例えば、下記のように String の文字列長と文字列を返す場合、s1 は関数 get_length にOwnerが譲渡されるため、後続処理では s1 を利用できなくなります。

println! の場合は譲渡されないため、何故なのかは今後記載します。

fn main() {
    let s1: String = String::from("Hello World!!");

    let (len, s2): (usize, String) = get_length(s1);

    println!("s1 -> {}", s1);
    println!("s2 -> {}", s2);
    println!("len -> {}", len);
}

fn get_length(s: String) -> (usize, String) {
    let len = s.len();
    (len, s)
}
error[E0382]: borrow of moved value: `s1`
 --> demo.rs:6:23
  |
2 |     let s1: String = String::from("Hello World!!");
  |         -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
3 | 
4 |     let (len, s2): (usize, String) = get_length(s1);
  |                                                 -- value moved here
5 | 
6 |     println!("s1 -> {}", s1);
  |                          ^^ value borrowed here after move

そのため、 s1Clone をしてあげる事によって、 s1 自体Ownerは残り、後続処理でも使用可能となります。

fn main() {
    let s1: String = String::from("Hello World!!");

    let (len, s2): (usize, String) = get_length(s1.clone());

    println!("s1 -> {}", s1);
    println!("s2 -> {}", s2);
    println!("len -> {}", len);
}

fn get_length(s: String) -> (usize, String) {
    let len = s.len();
    (len, s)
}
s1 -> Hello World!!
s2 -> Hello World!!
len -> 13

Copy 可能な場合は、関数に代入した場合でも後続処理で使用可能です。

fn main() {
    let x: i32 = 5;
    let y: i32 = example(x);

    println!("x -> {}", x);
    println!("y -> {}", y);
}

fn example(x: i32) -> i32 {
    let y = x;
    y
}
x -> 5
y -> 5

上記の場合、他言語と類似していますが、example 内の yexample 外では使用できず、 example が終了した段階(範囲外)になった段階で値は削除されます。


最後に

OwnershipのRuleを別解釈で記載すると、

  • 所有権が代わるか否か( Copy できるかどうか)
  • 所有者の範囲外(Scope外)である場合は値が削除される

だと考えます。

関数のセクションで記載できなかった、 borrowing に関してもOwnershipに関わる重要な点なので、それは次回に記載します。現段階では、関数のポインタ的なものなんだろうなぁ程度の認識なので・・・

また、 println! の場合でのOwnerが譲渡されない点についても、気になるので別で記載する予定です。