Rustとはなにか
RustはFirefoxのMozillaが関わっている言語で、速さ、メモリの安全性、安全な並行性を保証するシステムプログラミングのための言語です。特に速さはかなりのもので最新版のRustはC/C++なみに速いようです。私は少しだけ触ってみたのですが、個人的な感想としては、ちゃんとした書き方(immutability、参照や所有権の理解等)をしていない場合にコンパイルエラーもしくは警告になってくれるC++という気がします。
実際にRustをプロダクトに使用している例としてはFirefoxやRedox OS等が挙げられます。
Rust Playgroundでコードの実行結果を試しながら進めるとわかりやすいと思います。
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021
所有権
そんなRustですが習得が難しいと言われます。特に難しいと言われる部分が所有権/借用、ライフタイムです。
まずは所有権の説明からします。
Copy, Clone
まず以下のコードはエラーになります。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let some_struct = SomeStruct { a: 1 };
print_var(some_struct);
print_var(some_struct); // エラー!!!
}
fn print_var(variable: SomeStruct) {
println!("{:?}", variable);
}
なんでエラーになったのか?それはmain関数のなかのsome_structが2回関数に渡されているためです。
なぜ同一スコープのなかで2回続けて同じ変数を関数に渡してはいけないのでしょうか。それは、関数に変数を渡すと、その変数の所有権がその関数に移るからです。
このコードでいうとsome_struct
変数をprint_var
関数に渡してすでにこの変数の所有権がprint_var
に移っているのにもかかわらずmain関数が再度some_struct
をprint_var
関数に渡してしまっています。
これを防ぐにはどうすればいいでしょうか?ややパフォーマンスに影響がある(と言ってもよほど繰り返さないと人間にわかるような差にはならないですが)ので、パフォーマンスが重要な箇所では避けたほうがいいですが、Cloneを使うのが一番簡単です。
#[derive(Debug, Clone)]
struct SomeStruct {
a: i32
}
fn main() {
let some_struct = SomeStruct { a: 1 };
print_var(some_struct.clone()); // cloneで中身は同じでも別の変数を関数に渡します
print_var(some_struct); // エラーにならなくなりました。
}
fn print_var(variable: SomeStruct) {
println!("{:?}", variable);
}
Clone以外にCopyを使うこともできます。CopyはCloneと違ってパフォーマンスにほぼ影響を与えませんのでどんどん使うことができます。ただし、Copyが実装できるのはint, floatなどの値だけで、Stringやvec(配列)、あるいはこれらを内部に持つStructはCopyを実装することができません。
#[derive(Debug, Clone, Copy)] // このStructはStringなどを内部に持たないのでCopy可能
struct SomeStruct {
a: i32
}
fn main() {
let some_struct = SomeStruct { a: 1 };
print_var(some_struct); // Copyが実装されている場合暗黙的にCopyされた値が渡されます
print_var(some_struct); // エラーにならなくなりました。
}
fn print_var(variable: SomeStruct) {
println!("{:?}", variable);
}
ではStringやvec(配列)、あるいはこれらを内部に持つStructでCopyをしたい場合どうすればいいでしょうか。その場合はStringやvecの参照 (Reference)
をStructに持たせるように変えればCopyの実装ができます。
あるいはより単純なのはそのStructの参照を関数に渡すことでしょう。
例えばこうです。
#[derive(Debug)]
struct SomeStruct {
a: String
}
fn main() {
let some_struct = SomeStruct { a: "テストです".to_string() };
print_test(&some_struct);
print_test(&some_struct);
print_test(&some_struct);
print_test(&some_struct); // 参照を渡せばCopy, Cloneがなくても何度渡してもエラーにならない
}
fn print_test(variable: &SomeStruct) {
println!("{:?}", variable)
}
Read onlyの参照とMutable(変更可能)な参照
Rustでは参照は以下のように使います。
これはSomeStructにCopyもCloneも実装していませんが実行することができます。
#[derive(Debug)] // 参照を渡す場合CopyもCloneもいらない
struct SomeStruct {
a: i32
}
fn main() {
let some_struct = SomeStruct { a: 1 };
print_var(&some_struct); // 参照を関数に渡します
print_var(&some_struct); // 参照は何度関数に渡してもエラーになりません。
}
fn print_var(variable: &SomeStruct) {
println!("{:?}", variable);
}
こういうふうに関数の引数に参照を取ることを借用 (borrow)
と呼びます。
ただし、これはRead onlyな参照であり、SomeStructの参照の値を変えようとするとエラーになります。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let some_struct = SomeStruct { a: 1 };
print_var(&some_struct);
}
fn print_var(variable: &SomeStruct) {
variable.a = 1; // エラーになる!!
println!("{:?}", variable);
}
Rustではデフォルトでは変数がRead onlyで作成されます。これをmut
をつけてMutableな変数にします。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let mut some_struct = SomeStruct { a: 1 };
print_var(&mut some_struct);
}
fn print_var(variable: &mut SomeStruct) {
variable.a = 1; // エラーにならない!!
println!("{:?}", variable); // 1で表示される!
}
こうすればエラーになりません。
じゃあCopyだのCloneだの面倒くさいことしなくても全部の関数にMutableな参照を渡すようにすればよくね?と思いましたか?
Mutableな参照を渡す行為(mutable borrow)には制限があって、例えばすでにRead Onlyで借用がされているのに同時にMutableな借用をすることはできません。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let mut some_struct = SomeStruct { a: 1 };
let other_ref = &some_struct; // immutable borrow Read onlyの借用
mut_print_var(&mut some_struct); // mutable borrow Mutableの借用
print_var(other_ref); // Read onlyの借用がここで使われるのでエラー!!
}
fn mut_print_var(variable: &mut SomeStruct) {
variable.a = 1;
println!("{:?}", variable);
}
fn print_var(variable: &SomeStruct) {
println!("{:?}", variable);
}
順番を入れ替えるとコンパイルが通ります。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let mut some_struct = SomeStruct { a: 1 };
mut_print_var(&mut some_struct); // mutable borrow Mutableの借用
let other_ref = &some_struct; // immutable borrow Read onlyの借用
print_var(other_ref); // Read onlyの借用がここで使われる
}
fn mut_print_var(variable: &mut SomeStruct) {
variable.a = 1;
println!("{:?}", variable);
}
fn print_var(variable: &SomeStruct) {
println!("{:?}", variable);
}
ただし、参照を扱う場合、値が入っているはずなのに解放済みのアドレスを指すポインター(dangling pointer)を参照してしまうと致命的なバグになったりエラーになったりします。そのため、Rustはこれをライフタイムという仕組みでコンパイル時に検出できるようにしています。
ライフタイム
ライフタイム(寿命、生存期間)というのは読んで字のごとくそのデータがどれくらいの間有効か(どのくらいの間そのデータが生きているのか)を指すものです。これは他言語で言うスコープを抽象化したものだと考えればいいでしょう。
スコープとJavascript
ライフタイムはスコープと関係があるとお話しました。この「スコープ」は他言語にも存在します。
例えば以下のJavascriptのコードはコンソールに"hello"と表示します。変数aに"hello"というStringが入っているからですね。
function test() {
let a = "hello";
console.log(a);
}
test(); // hello
(なおjavascriptではvarではなくletと書くとブロックスコープになります。)
しかし、以下のコードは変数aに"hello"というStringが入っていないのでundefinedになってしまいます。
function test() {
let a = "hello";
}
test();
console.log(a);
test関数内部で変数aに"hello"というStringを入れているはずなのにconsole.logが変数aを参照したときにundefinedになるのはなぜか?というとスコープが違うからですね。変数aはスコープから外れた時点で参照できなくなります。
以下の書き方は(普通こんなふうには書かないですが)変数aを参照可能な場所に移しているので"hello"と表示されます。
let a;
function test() {
a = "hello";
}
test();
console.log(a);
ポイントは「変数がスコープから外れたときに参照できなくなる」ということです。
Rustでもスコープから外れればデータは参照できなくなるのですが、ライフタイムを使用することで明示的にその挙動を定義することができます。
Rustの場合
これはSomeStructの参照型のvariable1
, variable2
という引数をとり、aが大きいほうを返す(参照を返す)関数です。ですがこれはエラーになります。それは返却値がどれくらいのライフタイムを持つかをコンパイラが推測(infer)できないからです。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let some_struct1 = SomeStruct { a: 1 };
let some_struct2 = SomeStruct { a: 2 };
println!("{:?}", compare_b(&some_struct1, &some_struct2));
}
fn compare_b(variable1: &SomeStruct, variable2: &SomeStruct) -> &SomeStruct {
if variable1.a > variable2.a {
return variable1;
} else {
return variable2
}
}
そこでライフタイムを指定するようにします。以下のコードにある'a
がライフタイムです。
こちらは少し変わって、SomeStructの参照型のvariable1
, variable2
という引数をとり、aが大きいほうを返す(参照を返す)、返却値の参照は引数と同じライフタイムを持つという意味です。
#[derive(Debug)]
struct SomeStruct {
a: i32
}
fn main() {
let some_struct1 = SomeStruct { a: 1 };
let some_struct2 = SomeStruct { a: 2 };
println!("{:?}", compare_b(&some_struct1, &some_struct2));
}
fn compare_b<'a>(variable1: &'a SomeStruct, variable2: &'a SomeStruct) -> &'a SomeStruct {
if variable1.a > variable2.a {
return variable1;
} else {
return variable2
}
}
これなら正常に動かすことができます。
Rustのスコープとライフライム
以下はRustのドキュメントからの抜粋ですが、スコープが3つあるのがわかります。
fn main() { // 一番上のスコープAが始まります。
let i = 3; // 変数`i`が生まれました!
{ // スコープBが始まります
let borrow1 = &i; // 変数`i`のデータを「借用」します
println!("borrow1: {}", borrow1);
} // スコープBが終わるのでここで変数`borrow1`のライフタイム終了
{ // ここでスコープCが始まります
let borrow2 = &i; // 変数`i`のデータを「借用」します
println!("borrow2: {}", borrow2);
} // スコープCが終わるのでここで変数`borrow2`のライフタイム終了
} // スコープAの終了、変数`i`のライフタイム終了
これを実行すると以下のようになります。
borrow1: 3
borrow2: 3
ここで重要なのが変数 i のスコープは変数宣言時に決まりますが、参照(borrow1, borrow2)のスコープは「借用」が起こった時点で決まるということです。
あえてそれぞれのスコープに名前をつけてみましょう。
fn main() { // 一番上のスコープAが始まります。
let i = 3; // 変数`i`が生まれました!
'b: { // スコープBが始まります
let borrow1 = &i; // 変数`i`のデータを「借用」します
println!("borrow1: {}", borrow1);
} // スコープBが終わるのでここで変数`borrow1`のライフタイム終了
'c: { // ここでスコープCが始まります
let borrow2 = &i; // 変数`i`のデータを「借用」します
println!("borrow2: {}", borrow2);
} // スコープCが終わるのでここで変数`borrow2`のライフタイム終了
} // スコープAの終了、変数`i`のライフタイム終了
↑のコードはerror[E0658]: labels on blocks are unstableというコンパイルエラーがでて動かないのですが、このそれぞれの'b, 'cがRustで言うライフタイムになります。'bと'cは共に一番上のスコープの下にありますが、この一番上のスコープを仮に'xとすると'bと'cは'xのsubtypeであるといいます。
ライフタイムは実際には以下のように使います。以下のstrcut Borrowedはライフタイム'aが指定されているので変数singleは'aというライフタイムで解放されます。
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
fn main() {
let x = 10;
let single = Borrowed(&x);
println!("x is borrowed in {:?}", single);
}
なので、以下のように長いライフタイムを持つ変数に短いライフタイムを持つ参照を入れて使用しようとするとエラーになります。
fn substitute<str>(input: &mut str, val: str) {
*input = val;
}
fn main() {
let mut static_string: &'static str = "staticでmutableなstringです";
{
let shorter_lifetime = String::from("ライフタイムが短いです");
let ref_shorter_lifetime: &str = &shorter_lifetime; // この参照のライフタイムはこの{}内になります。
substitute(&mut static_string, ref_shorter_lifetime); // static_stringにref_shorter_lifetimeを入れます
}
println!("{}", static_string); // エラー!
}
以下のようにライフタイムを合わせるとエラーになりません。
fn substitute<str>(input: &mut str, val: str) {
*input = val;
}
fn main() {
let mut static_string: &'static str = "staticでmutableなstringです";
{
let shorter_lifetime: &'static str = "ライフタイムが長くなりました。"; //ライフタイムを合わせる
let ref_shorter_lifetime: &str = &shorter_lifetime;
substitute(&mut static_string, &ref_shorter_lifetime);
}
println!("{}", static_string);
}
Coercion
なお長いライフタイム、短いライフタイムの2つがある場合、短いほうのライフタイムに合わせることができます。これをCoercion(強制)といいます。
// この場合、引数firstとsecondは同じライフタイムを持っています。
// Rustはできるだけ短いライフタイムを推測しようとします。
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
first * second
}
// `<'a: 'b, 'b>`はライフタイム`'a`は「少なくとも」`'b`と同じくらいの長さを持つと読みます。
// この場合`&'a i32`というライフタイムの参照を受け取りますが、Coercionの結果`&'b i32`を返します。
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
first
}
fn main() {
let first = 2; // ライフタイムはsecondより長い
{
let second = 3; // ライフタイムはfirstより短い
println!("2つの数字の積は{}", multiply(&first, &second));
println!("{}が変数firstに入っています。", choose_first(&first, &second));
};
}
static
以下のようにstaticというライフタイムを指定することができます。
これはプログラムが終了するまでデータを有効にするというライフタイムになります。
コンスタント(定数)等に使用されます。
ライフタイムの例で出た以下のようなStringリテラルはstaticなライフタイムを持つ例です。
let mut static_string: &'static str = "staticでmutableなstringです";
以下のようにも使えます。
// `'static`ライフタイムでコンスタントNUMを宣言
static NUM: i32 = 18;
// staticなライフタイムを持つ`NUM`への参照を返します。
// この関数はNUMのライフタイムを引数のライフタイム'aにcoerceし、NUMの参照を返します。
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
&NUM
}
fn main() {
{
// `string`リテラルを作りプリントします。
let static_string : &'static str = "read-onlyのmemoryの中にあるデータです";
println!("static_string: {}", static_string);
// `static_string`変数がスコープ外に出ると参照にはアクセスできなくなりますが
// データはバイナリ内に残ります。
}
{
let lifetime_num = 9;
// `NUM`のライフタイムを`lifetime_num`のライフタイムにcoerceします。
let coerced_static = coerce_static(&lifetime_num);
println!("coerced_static: {}", coerced_static);
}
// NUMはアクセス可能です。
println!("NUM: {} はまだアクセス可能です。", NUM);
}
structにライフタイムを設定する
以下のようにstructにライフタイムを設定することができます。が、これはエラーになります。
struct Person<'a:'c, 'b:'c, 'c> {
pub age: &'a &'c i32,
pub name: &'b mut &'c str,
}
fn main() {
let tanaka_age: i32 = 25;
let ref_tanaka_age: &i32 = &tanaka_age;
let mut tanaka_name: &'static str = "Tanaka Taro";
let tanaka = Person { age: &ref_tanaka_age, name: &mut tanaka_name };
}
こうするとコンパイルが通ります。
struct Person<'a:'c, 'b:'c, 'c> {
pub age: &'a &'c i32,
pub name: &'b mut &'c str,
}
fn main() {
let tanaka_age: i32 = 25;
let ref_tanaka_age: &i32 = &tanaka_age;
let mut tanaka_name: &str = "Tanaka Taro"; // staticを消す
let tanaka = Person { age: &ref_tanaka_age, name: &mut tanaka_name };
}
これはエラーの原因が変数tanaka(staticではない)とtanaka_name(static)のライフタイムが違ったためです。Person structの定義は以下のように行ったのでPerson型の変数は少なくともage, name以上のライフタイムを持つ必要が有ります。
struct Person<'a:'c, 'b:'c, 'c> {
pub age: &'a &'c i32,
pub name: &'b mut &'c str,
}
Coercionのところでも書きましたが、このような場合、以下の様な意味になります。
// `<'a: 'b, 'b>`はライフタイム`'a`は「少なくとも」`'b`と同じくらいの長さを持つと読みます。
// この場合`&'a i32`というライフタイムの参照を受け取りますが、Coercionの結果`&'b i32`を返します。
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
first
}
参考文献
- https://medium.com/nearprotocol/understanding-rust-lifetimes-e813bcd405fa
- https://doc.rust-lang.org/rust-by-example/scope/lifetime.html
わかりやすい資料