Edited at

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

More than 1 year has passed since last update.

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

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