Rust
slice
string
変数

[Rust] &strとStringを理解しようと思ったらsliceやmutを理解できてないことに気づいた話

Rust入門メモ。

Rustは文字列を表す型として &strString がある
これが、結構ややこしくて、どういうときにどっちを使うべきなのかがよくわからない

環境

OS: macOS High Sierra 10.13.3
Rust: rustc 1.26.0-nightly (c08480fce 2018-03-23)

Rustの文字列型

Rustの日本語Document: 文字列

Rustには主要な文字列型が二種類あります。&str と Stringです。 まず &str について説明しましょう。 &str は「文字列スライス」と呼ばれます。 文字列スライスは固定サイズで変更不可能です。文字列スライスはUTF-8のバイトシーケンスへの参照です。

Rustには &str だけでなく、 String というヒープアロケートされる文字列もあります。 この文字列は伸張可能であり、またUTF-8であることも保証されています。 String は一般的に文字列スライスを to_string メソッドで変換することで作成されます。

https://qiita.com/Mizunashi_Mana/items/db88cb0bff002abce1ae

↑このQiitaの解説が一番わかりやすい

Rustには文字列を表す型が二つあります。
一つはプリミティブ型のstr、もう一つが標準ライブラリが提供するStringです。といってもそれほど大した違いはありません。
str型はu8のスライス、String型はu8のベクタと同じです。ただ両方とも、列の内容としてUTF-8しか許可していないというその一点だけが異なります。なので、実質並びに制限のある8byteの配列スライスとベクタです。strの方はプリミティブ型ですが、Stringの方は標準ライブラリの提供型なので実装が単純に見て取れます。

&str -> slice
String -> Vector

ということだ。

そもそも、sliceってちゃんと理解してないな…
だから、まずはsliceに関して理解していこうと思う

sliceと配列とVector

https://qiita.com/Mizunashi_Mana/items/db88cb0bff002abce1ae#%E6%96%87%E5%AD%97%E5%88%97%E3%81%A8%E6%96%87%E5%AD%97%E5%88%97%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9%E3%81%AE%E9%81%95%E3%81%84

ここにも書いてある

つまり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どっかへの参照
StringVec<u8>

なので、

  • 参照だけでいい場合は、&strを使う
  • 文字列の中身を書き換えたい場合はStringを使うとよい

ってことなんだろうか。

&strで良い例

https://qiita.com/Mizunashi_Mana/items/db88cb0bff002abce1ae#%E9%96%A2%E6%95%B0%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6

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の方が良い例

https://qiita.com/Mizunashi_Mana/items/db88cb0bff002abce1ae#%E9%96%A2%E6%95%B0%E3%81%AE%E8%BF%94%E3%82%8A%E5%80%A4%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6

文字列を破壊的に書き換えたい場合

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でいいのではなかろうかという気さえしてくる。

結局、どう使い分ければよいのか、はっきりと理解できずに終わってしまった。