Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 8. Collections - 2 string

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、Collectionsの string について学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


string

公式ドキュメント(Storing UTF-8 Encoded Text with Strings - The Rust Programming Language)より、

  • Rustの文字列はstring slice strstr を指す &str がコア
  • StringUTF-8エンコード
  • OsStringOsStrCStringAPIドキュメントを参照すること

文字列の生成

new を利用することによって、文字列の生成が可能です。

fn main() {
    let s = String::new();
    println!("s -> {:#?}", s);
}
s -> ""

また、各所に出てきた from により、文字リテラルstr )からの生成が可能です。

fn main() {
    let s = String::from("Hello Rust!!");
    println!("s -> {:#?}", s);
}
s -> "Hello Rust!!"

上記は、下記の処理と同等です。

fn main() {
    let data: &str = "Hello Rust!!";
    let s = data.to_string();
    println!("s -> {:#?}", s);
}
s -> "Hello Rust!!"

文字列の更新

push_str による文字列の更新が可能です。

fn main() {
    let mut s = String::from("hoge");
    s.push_str(", fuga");
    println!("s -> {:#?}", s);
}
s -> "hoge, fuga"

push_str はownerを必ずしも取得したいわけでは無いため、下記のように push_str 後であっても対象は使用できます。

fn main() {
    let mut s1 = String::from("hoge");
    let s2 = ", fuga";
    s1.push_str(s2);
    println!("s1 -> {:#?}", s1);
    println!("s2 -> {:#?}", s2);
}
s1 -> "hoge, fuga"
s2 -> ", fuga"

文字列連結

+ を利用することで連結が可能です。

fn main() {
    let s1 = String::from("hoge");
    let s2 = String::from("fuga");

    let s = s1 + ", " + &s2;
    println!("s -> {:#?}", s);
}
s -> "hoge, fuga"

上記で着目すべきは &s2 であることです。 s1 に対して + 演算子&str として s2 が利用され、新しいStringを生成している状態です。この時、 s1 のownerが s に譲渡されているため、利用はできません。

fn main() {
    let s1 = String::from("hoge");
    let s2 = String::from("fuga");

    let s = s1 + ", " + &s2;
    println!("s -> {:#?}", s);
    // NOTE: value borrowed here after move
    // println!("s1 -> {:#?}", s1);
    println!("s2 -> {:#?}", s2);
}
s -> "hoge, fuga"
s2 -> "fuga"

また、 format! マクロを利用して文字列の連結も可能です。この場合、 s1演算子利用ではないため、 s1 のowner自体は渡らず、後続処理でも利用が可能です。

fn main() {
    let s1 = String::from("hoge");
    let s2 = String::from("fuga");

    let s = format!("{}, {}", s1, s2);
    println!("s -> {:#?}", s);
    println!("s1 -> {:#?}", s1);
    println!("s2 -> {:#?}", s2);
}
s -> "hoge, fuga"
s1 -> "hoge"
s2 -> "fuga"

文字列でのインデックス

下記のように、 String に対してのindexの利用は出来ません。

fn main() {
    let s1 = String::from("hoge");
    let s = s1[0];
    println!("s -> {:#?}", s);
}
error[E0277]: the type `std::string::String` cannot be indexed by `{integer}`
 --> src/demo.rs:3:10
  |
3 |     let s = s1[0];
  |             ^^^^^ `std::string::String` cannot be indexed by `{integer}`
  |
  = help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String

これは、 StringVec<u8> のラッパーであることが起因しています。

例えば、下記のように、英語と日本語(1バイト文字と2バイト文字)を比較した場合、lengthは人間が思っているlenとはなりません。

fn main() {
    let len1 = "Hello".len();
    let len2 = "おはよう".len();
    println!("len1 -> {}, len2 -> {}", len1, len2);
}
len1 -> 5, len2 -> 12

u8 として考えた場合、 おはよう から を呼ぶだけでも解釈が必要になります。そのため、String にindexを利用させてしまった場合、 を取得するのではなく、 u8 としての解釈が文字によって変化してしまうためです。

それだけではなく、index付けに一定の時間が予測され、 String として最初から最後までindexの内容を調べなければならない状況となってしまうためです。

もし取得したい場合は、 chars() によりUnicodeとしてsliceさせて nth() により取得することが可能です。

fn main() {
    let data1 = String::from("Hello");
    let data2 = String::from("おはよう");

    let s1 = data1.chars().nth(0);
    let s2 = data2.chars().nth(0);
    println!("s1 -> {:#?}", s1);
    println!("s2 -> {:#?}", s2);
}
s1 -> Some(
    'H',
)
s2 -> Some(
    'お',
)

最後に

文字コードは扱うのが難しいですね・・・

次回はHash map!