Rust
が良い、Rust
が良いと一部のエンジニアから聞いていたのですが、どうせ文法的にはGolang
とScala
の中間くらいでしょう、くらいに考えていましたが、今は反省しています。大きなパラダイム・シフトがありました。
所有権
この概念がRust
の中でも大きく理解を難解にさせていると思います。しかし一方でこの概念は今までのプログラミングで実行時に問題になってきた内容を前倒してコンパイル時に解決する、という解決策をRust
は提供しているのです。
まずは普通の実行っぷりを書いてみます
fn main() {
let s = vec!["udon".to_string(),"ramen".to_string(),"soba".to_string()];
let t = s;
let u = t;
println!("u[0] is: {}", u[0]);
}
$ cargo run
Compiling hello v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/hello`
u[0] is: udon
そしてこちらが所有権のエラーが発生するパターンです。コンパイルエラーになっています。
fn main() {
let s = vec!["udon".to_string(),"ramen".to_string(),"soba".to_string()];
let t = s;
let u = s;
println!("u[0] is: {}", u[0]);
}
$ cargo run
Compiling hello v0.1.0
warning: unused variable: `t`
--> src/main.rs:3:9
|
3 | let t = s;
| ^ help: consider using `_t` instead
|
= note: #[warn(unused_variables)] on by default
error[E0382]: use of moved value: `s`
--> src/main.rs:4:9
|
3 | let t = s;
| - value moved here
4 | let u = s;
| ^ value used here after move
|
= note: move occurs because `s` has type `std::vec::Vec<std::string::String>`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: Could not compile `hello`.
To learn more, run the command again with --verbose.
Rust
では通常の代入処理時に値の所有権の移動を行います。上の例ではs -> t -> uと代入処理を行っており、その順で所有権が移動、最終的にuにアクセスして要素を表示するものになっています。対して下の例では、s -> t, s -> uと代入を行っております。最初、s -> tと所有権が移動したタイミングでsは値にアクセスする権限を持っていません。そのため、代入処理ができないため、エラーが発生するのです。
Rust
のすごいところはこれをコンパイラが解決しているところです。
もちろん代入処理だけでなく、所有権を失った変数にアクセスしようものならコンパイルエラーが発生します
fn main() {
let s = vec!["udon".to_string(),"ramen".to_string(),"soba".to_string()];
let t = s; // sはここで所有権を失う
let u = t;
println!("s[0] is: {}", s[0]); // 所有権のないsにアクセス!
}
$ cargo run
Compiling hello v0.1.0
warning: unused variable: `u`
--> src/main.rs:4:9
|
4 | let u = t;
| ^ help: consider using `_u` instead
|
= note: #[warn(unused_variables)] on by default
error[E0382]: use of moved value: `s`
--> src/main.rs:5:29
|
3 | let t = s;
| - value moved here
4 | let u = t;
5 | println!("s[0] is: {}", s[0]);
| ^ value used here after move
|
= note: move occurs because `s` has type `std::vec::Vec<std::string::String>`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: Could not compile `hello`.
To learn more, run the command again with --verbose.
大切なことなのでもう一度いいます。これをコンパイル時にRust
は解決しています。
上記のコードをC++
で書くと下記のようなコードになります。
#include <stdio.h>
#include <string>
#include <iostream>
#include <vector>
int main(){
auto s = new std::vector<std::string>();s->push_back("udon");s->push_back("ramen");s->push_back("soba");
auto t = s; s = NULL;
auto u = s; s = NULL;
std::cout << (*s)[0] << std::endl;
return 0;
}
でコンパイルできてしまいます。
$ g++ -std=c++11 main.cpp
$ ./a.out
Segmentation fault: 11
実行時エラーになってしまうわけです。大規模になればなるほど、バグを探すのがめんどくさいあれです。
NULLを代入しなければもちろんアクセスできますが、メモリ解放の難解さを内包したままになるのでRust
に寄せて記述しています。
この機能を持つプログラミング言語は今の所Rust
くらいしかないのではないでしょうか。
ほら大規模開発をRust
でしたくなってきたでしょう?とはいえ、まだライブラリが貧弱なので一筋縄にはいかないでしょうけども。