このページでは Rust の一番基本的な文字列型を理解するために String
と &str
についてまとめる。
String の特徴
- 実体は
Vec<u8>
- ただしこの
Vec<u8>
は有効な UTF-8 バイト列に限定される
- ただしこの
- 伸長できる
- 変更できる
- 標準ライブラリの型
- なので、エディタで適当にジャンプすれば実装が読める
実際に String
の実装を読んでみると、Vec<u8>
を持つ構造体であることがわかる。
pub struct String {
vec: Vec<u8>,
}
&str の特徴
- 文字列スライスとか stir と呼ぶ
- 実体は
&[u8]
-
String
のVec<u8>
と同じく、有効な UTF-8 バイト列に限定される
-
- サイズは固定
- 変更不可
- プリミティブの型
String を見かけるタイミング
頻出するので、いくつかを簡単に整理する。
生成
String::from
や &str
の to_string
で生成できる。
let string: String = String::from("foo");
let string: String = "foo".to_string();
"foo".to_string()
は内部で String::from(self)
になっているので、↑ の 2 つのコードのコストは同じ。
format! マクロ
format!
マクロの結果は String
になる。
let string: String = format!("id: {}", 42);
戻り値
戻り値は &str
より String
になることが多い。
たとえば次のように &str
を組み立てて返す get_dir
を実装してみても、これはコンパイルできない。
fn get_dir(sub_dir: &str) -> &str {
let dir = format!("data/{sub_dir}");
&dir // cannot return reference to local variable `dir`
// returns a reference to data owned by the current function
}
dir
は get_dir
の完了時に Drop されてしまうので、それより長生きする &str
としてスコープの外に出すことはできないからだ。
変更する
String
は変数や引数を変更したいときに使う。
let mut string: String = format!("dir/dir-{}", 1);
string.push_str("/application.log"); // dir/dir-1/application.log
文字列処理の結果
文字列処理の結果など、動的に定まる場合に使われる。
let string: String = "foo".to_uppercase(); // FOO
&str を見かけるタイミング
こちらも簡単に整理する。
生成
文字列リテラルで定義したものは &str
になる。
let str: &str = "foo";
部分文字列を返すような文字列処理の結果
文字列処理の結果でも、部分文字列を返すような処理では結果が &str
なこともある。
let str: &str = " foo ".trim(); // foo
let str: Option<&str> = "foo/bar".split('/').next(); // Some("foo")
String と &str の相互変換
String
→ &str
は &
で参照するだけでいい。
let str: &str = &string;
&str
→ String
は先ほど述べた通り String::from
や to_string
で行う。
let string: String = str.to_string();
相互変換を図で整理すると ↓ のようになる。
String を引数に使うときの注意
String
は Copy
トレイトを実装していないので、引数に使うと所有権は移動する。
つまり ↓ のコードは 1 度目の something
で string
の所有権を失っているので、コンパイルできない。
fn main() {
let string: String = "foo".to_string();
something(string);
something(string); // use of moved value: `string`
// value used here after move
}
fn something(_: String) {
println!("do something");
}
コンパイルしたければ something
の引数を &str
に変えたり、string.clone()
を行う必要がある。
fn main() {
let str: &str = "foo";
something(str);
something(str);
}
fn something(_: &str) {
println!("do something");
}
fn main() {
let string: String = "foo".to_string();
something(string.clone());
something(string.clone());
}
fn something(_: String) {
println!("do something");
}
Vec<u8> から String への変換
前のページで述べた通り、バイト列 ( Vec<u8>
) の全てが UTF-8 として有効なバイト列なわけではない。
しかし String
は UTF-8 として有効な Vec<u8>
のみを保持する構造体であるため、Vec<u8>
→ String
の変換は失敗する可能性がある。
従って String::from_utf8
は Vec<u8>
から String
を生成するが、結果の型は String
ではなく Result<String, FromUtf8Error>
である。
↓ のコードは 16 進数表記の Vec<u8>
を String
にちゃんと変換できる例だ。
それぞれ あ
と コピーライトマーク を示すバイト列である。
let ns: Vec<u8> = vec![0xe3, 0x81, 0x82];
let string: Result<String, FromUtf8Error> = String::from_utf8(ns);
// Ok("あ")
let ns: Vec<u8> = vec![0xc2, 0xa9];
let string: Result<String, FromUtf8Error> = String::from_utf8(ns);
// Ok("©")
↓ のコードは UTF-8 としては不正なビットがある Vec<u8>
や Unicode 範囲外の Vec<u8>
が変換に失敗する例だ。
let ns: Vec<u8> = vec![0x80];
let string: Result<String, FromUtf8Error> = String::from_utf8(ns);
// Err(FromUtf8Error { bytes: [128], error: Utf8Error { valid_up_to: 0, error_len: Some(1) } })
let ns: Vec<u8> = vec![0xf4, 0x90, 0x80, 0x80];
let string: Result<String, FromUtf8Error> = String::from_utf8(ns);
// Err(FromUtf8Error { bytes: [244, 144, 128, 128], error: Utf8Error { valid_up_to: 0, error_len: Some(1) } })
このように、全ての Vec<u8>
が String
に変換できるわけではない。
図に書き足しておく。
整理
-
String
と&str
の相互変換は容易 -
String
はVec<u8>
を保持する構造体 -
Vec<u8>
→String
の変換は失敗する可能性がある
参考