【Rust】がばがばRust独学 - 4. Ownership - 2 Copy/Clone/関数
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、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
であるものは、別変数にコピーすることが可能になります。ドキュメントからの引用より、 interger
、 boolean
、float
、 char
が可能であり、対象の型で構成された 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
としても、y
に x
がコピーされるため、 y = x
をした段階でも x
、 y
共に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
が用意されております。
そして、 Trait std::marker::Copy
を各型への Copy
の実体化をしています。
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
Clone
は Copy
と同様にtraitで定義されています。
interger
、 boolean
、float
、 char
はマクロにより、 Clone
が定義されています。
そのため、下記のように、 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
は Vec
により生成されていることも見受けられます。
関数
関数の場合は、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
そのため、 s1
を Clone
をしてあげる事によって、 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
内の y
は example
外では使用できず、 example
が終了した段階(範囲外)になった段階で値は削除されます。
最後に
OwnershipのRuleを別解釈で記載すると、
- 所有権が代わるか否か(
Copy
できるかどうか) - 所有者の範囲外(Scope外)である場合は値が削除される
だと考えます。
関数のセクションで記載できなかった、 borrowing
に関してもOwnershipに関わる重要な点なので、それは次回に記載します。現段階では、関数のポインタ的なものなんだろうなぁ程度の認識なので・・・
また、 println!
の場合でのOwnerが譲渡されない点についても、気になるので別で記載する予定です。