【Rust】がばがばRust独学 - 8. Collections - 2 string
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、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
str
とstr
を指す&str
がコア String
はUTF-8でエンコードOsString
、OsStr
、CString
はAPIドキュメントを参照すること
文字列の生成
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
これは、 String
が Vec<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!