Rust入門メモ。
Rustは文字列を表す型として &str
と String
がある
これが、結構ややこしくて、どういうときにどっちを使うべきなのかがよくわからない
環境
OS: macOS High Sierra 10.13.3
Rust: rustc 1.26.0-nightly (c08480fce 2018-03-23)
Rustの文字列型
Rustには主要な文字列型が二種類あります。&str と Stringです。 まず &str について説明しましょう。 &str は「文字列スライス」と呼ばれます。 文字列スライスは固定サイズで変更不可能です。文字列スライスはUTF-8のバイトシーケンスへの参照です。
Rustには &str だけでなく、 String というヒープアロケートされる文字列もあります。 この文字列は伸張可能であり、またUTF-8であることも保証されています。 String は一般的に文字列スライスを to_string メソッドで変換することで作成されます。
↑このQiitaの解説が一番わかりやすい
Rustには文字列を表す型が二つあります。
一つはプリミティブ型のstr、もう一つが標準ライブラリが提供するStringです。といってもそれほど大した違いはありません。
str型はu8のスライス、String型はu8のベクタと同じです。ただ両方とも、列の内容としてUTF-8しか許可していないというその一点だけが異なります。なので、実質並びに制限のある8byteの配列スライスとベクタです。strの方はプリミティブ型ですが、Stringの方は標準ライブラリの提供型なので実装が単純に見て取れます。
&str -> slice
String -> Vector
ということだ。
そもそも、sliceってちゃんと理解してないな…
だから、まずはsliceに関して理解していこうと思う
sliceと配列とVector
ここにも書いてある
つまりsliceってのは、 配列とかVectorへの参照ということですかね。
sliceの型は &[T]
なので、参照なのはわかる
ちなみに、文字列リテラルの型は &’static str
ちなみに、Goのsliceとarrayの解説を見つけた
https://qiita.com/seihmd/items/d9bc98a4f4f606ecaef7
これを見ていて、いろいろ気になったので
Rustでどうなるのか気になる事をやってみることにした
配列の参照(スライス)を別の変数に束縛して、ポインタを確認してみる
let a: [i32; 2] = [1,2];
let b = &a;
// これは同じ
println!("{:p}", &a);
println!("{:p}", b);
0x7ffee9a76dd8
0x7ffee9a76dd8
配列を別の変数にそのまま代入してみる
let a: [i32; 2] = [1,2];
let b = a;
// これは違う
println!("{:p}", &a);
println!("{:p}", &b);
0x7ffee9a76eb8
0x7ffee9a76ec0
これはコピーされる(?)ので、参照先が違うようだ
配列を書き換える
let mut a = [1,2,3];
let b = a; // ここでコピーされる?
a[0] = 0;
println!("{:?}", a); // #=> [0, 2, 3]
println!("{:?}", b); // #=> [1, 2, 3]
これもコピーされるっぽいので、bは安全
スライスを書き換える
let a = &mut [1,2,3];
let b = a;
a[0] = 0;
println!("{:?}", a); // #=> [0, 2, 3]
println!("{:?}", b); // #=> [1, 2, 3]
さて、これはコンパイルエラーになる
error[E0382]: use of moved value: `*a`
--> main.rs:56:9
|
55 | let b = a;
| - value moved here
56 | a[0] = 0;
| ^^^^^^^^ value used here after move
|
= note: move occurs because `a` has type `&mut [i32; 3]`, which does not implement the `Copy` trait
error[E0382]: use of moved value: `a`
a自体が既にsliceなので、所有権はbに代入した時点でmoveされる(?)
つまり、bに代入した時点でaはもういじれなくなるということですかね
これは、めんどくさいように見えて、実は超安全だなぁ、と思った。
だって、書き換えられる人は一人だけで、bをこの先いじいじしてる間に、aを誰か別のやつが書き換える、なんてことが出来ないのだから
書き換えるどころか、参照することさえできない。printlnしようとするとコンパイルエラー。
(これ、かっこいいなぁ。)
mutableにしたい時は
let mut a = 1;
a = 2;
println!("{:?}", a);
こんな風に let mut
って定義する
(ちなみに let mut a = 1;
はwarningが出ます)
しかしながら、mutableなsliceを定義する時は
let mut a = &[1];
a[0] = 0;
println!("{:?}", a);
これだとコンパイルエラー
error[E0594]: cannot assign to indexed content `a[..]` of immutable binding
--> main.rs:70:9
|
70 | a[0] = 0;
| ^^^^^^^^ cannot mutably borrow field of immutable binding
これは、何故か。
答えはここに書いてある んだけど。
let mut x = 1;
これ(x)は、 ミュータブルな変数束縛
let mut x = 1;
let y = &mut x;
これ(y)は、 ミュータブル参照
ということらしい。
y はミュータブル参照へのイミュータブルな束縛であり、 y を他の束縛に変える(y = &mut z)ことはできません。 しかし、y に束縛されているものを変化させること(*y = 5)は可能です。微妙な区別です。
とのこと。
やってみよう。
let a = &mut [1];
a[0] = 0;
println!("{:?}", a);
aはimmutableなのに、aの要素を書き換えることが出来る。
これが、ミュータブル参照へのイミュータブルな束縛ということか。
let a = &mut [1];
a[0] = 0; // これはできる
a = &mut [2]; // これはできない
println!("{:?}", a);
a
をmutableに束縛していないので、aという変数自体を書き換えることはできない。
んー、すっきりした!(気がする)
&strとString
&str
は どっかへの参照
String
は Vec<u8>
なので、
- 参照だけでいい場合は、&strを使う
- 文字列の中身を書き換えたい場合はStringを使うとよい
ってことなんだろうか。
&strで良い例
fn puts(s: &str) {
println!("{}", s);
}
受け取った文字列をただ標準出力に出力するだけ。
書き換えもしないので、ただただ参照を貰えばいい。
って書いてあると思うんですが
let a = "hoge".to_string();
println!("{:p}", &a);
puts(&a);
println!("{:p}", &a);
こんなことしてみると
0x7ffee61881d8
0x109e17018
0x7ffee61881d8
参照先が違う気がする。。。?
これは、何故だろう、、、
パフォーマンス上ではスライスの作成はコピーが発生しませんが、
って書いてあるけど、本当にコピー発生してないのだろうか
fn puts(s: &String) {
println!("{:p}", s);
}
こうすると、もちろん同じになる
0x7ffee3e521d8
0x7ffee3e521d8
0x7ffee3e521d8
んー。これは分からなくなってきた…
Stringの方が良い例
文字列を破壊的に書き換えたい場合
fn append_world(str: &mut String) {
str.push_str(" World");
}
let mut string = "Hello".to_string();
append_world(&mut string);
println!("{}", string); // => Hello World
まぁ、これはたしかにその通りだな、と思った。
でも、そこまでシビアなパフォーマンスを要求されない限りは、
破壊的に変更したい事ってあまりなくて、大抵は新しい文字列を返したい気がする。
戻り値に文字列
これは、よくあると思う。
&str
を返そうとするとコンパイルエラーになる
fn hoge() -> &str {
"hoge"
}
これは、そもそも ”hoge”
は &’static str
が型なので、型が合ってない
fn hoge() -> &str {
let a = "hoge".to_string();
&a
}
これは、型は合ってるけど、&a
を返すということは、実体の a
はこのscopeに生きているわけで
a
の生存期間以上に長い生存期間の参照を返す事になるので、lifetime parameterが必要になるらしい。
(ライフタイムについては、また別途)
つまり、実体のStringをそのまま返しちゃうほうがいい、ということですかね。
まとまらないまとめ
&strの引数の時に String -> &str でコピーされるとしたら、あまり&strにうまみがない(?)
だとすると、全部Stringでいいのではなかろうかという気さえしてくる。
結局、どう使い分ければよいのか、はっきりと理解できずに終わってしまった。