Chapter 02

String と &str

ほげさん
ほげさん
2023.03.01に更新

このページでは Rust の一番基本的な文字列型を理解するために String&str についてまとめる。

String の特徴

  • 実体は Vec<u8>
    • ただしこの Vec<u8> は有効な UTF-8 バイト列に限定される
  • 伸長できる
  • 変更できる
  • 標準ライブラリの型
    • なので、エディタで適当にジャンプすれば実装が読める

実際に String の実装を読んでみると、Vec<u8> を持つ構造体であることがわかる。

string.rs
pub struct String {
    vec: Vec<u8>,
}

&str の特徴

  • 文字列スライスとか stir と呼ぶ
  • 実体は &[u8]
    • StringVec<u8> と同じく、有効な UTF-8 バイト列に限定される
  • サイズは固定
  • 変更不可
  • プリミティブの型

String を見かけるタイミング

頻出するので、いくつかを簡単に整理する。

生成

String::from&strto_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
}

dirget_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;

&strString は先ほど述べた通り String::fromto_string で行う。

let string: String = str.to_string();

相互変換を図で整理すると ↓ のようになる。

String を引数に使うときの注意

StringCopy トレイトを実装していないので、引数に使うと所有権は移動する。

つまり ↓ のコードは 1 度目の somethingstring の所有権を失っているので、コンパイルできない。

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_utf8Vec<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 の相互変換は容易
  • StringVec<u8> を保持する構造体
  • Vec<u8>String の変換は失敗する可能性がある

参考

https://doc.rust-jp.rs/rust-by-example-ja/std/str.html

image

https://qiita.com/dalance/items/93928ad1e0f68f9baa2d

https://qiita.com/yagince/items/e7474839246ced595f7a