最近rustでストリークを続けてるんだけど、文字列が難しくて出てくると大体詰まる。
多分文字列というかrust自体の理解ができてないからなんだけど、取り合えず覚えた事をメモ。
(こういう記事無数にありそうだけど、きーたとかぜんでやってないだけ許してほしい)
(1) String型
基本として、rustの文字列はStringと&strの2種類ある。
まずは、標準ライブラリとして提供されてるらしいStringから。
fn main() { let mut s1 : String; s1.push_str("aaa"); }
これはコンパイルエラー。C++のstd::stringをイメージしてたので既に困惑した。newしないと駄目。
error[E0381]: used binding `s1` isn't initialized --> src/main.rs:3:5 | 2 | let mut s1 : String; | ------ binding declared here but left uninitialized 3 | s1.push_str("aaa"); | ^^ `s1` used here but it isn't initialized
文字列の追加はpush_str()、文字の追加はpush()。
fn main() { let mut s1 : String = String::new(); s1.push_str("aaa"); s1.push('A'); println!("{}", s1); }
aaaA
(2) &str型
次に、プリミティブな機能として提供されてる&str。こっちはnewいらない。
let mut s2 : &str; s2 = "bbb"; }
Stringを受け取れる。ただ見た目に反して(?)C++で言う参照ではないので、受け渡し先で文字列を書き換えても元のString変数の文字列は変化しない。コピーされているという理解でいいのかな。
let mut s3 : String = String::new(); s3.push_str("AAA"); let mut s4 : &str = &s3; s4 = "BBB"; println!("{}", s3); println!("{}", s4);
AAA BBB
文字列の一部だけを出力したい場合、普通の配列のようにindexで直接参照できないので、get()をする必要がある・・・ってあるけど、C++に比べてタイプ数多すぎるので、何か他のやり方があるような気がする。
取り合えず動くコード。「0..2」は0≦x<2なので、要素0から1の部分文字列が表示される。
println!("{}", s4.get(0..2).unwrap());
BB
(3) mutを付ける必要があるかどうか
&strの場合、1度しか設定しないならmutは不要。
let s5 : &str; s5 = "aaa";
以下は2回設定しているのでコンパイルエラー。
let s5 : &str; s5 = "aaa"; s5 = "bbb";
Stringの場合、1度しか設定しなくてもmutが必要。なんで・・・
以下はコンパイルエラー。
let s6 : String = String::new(); s6.push_str("AAA");
以下はOK。
let s7 : String = String::from("BBB");
但し、(2)で書いたように&strで受け取る場合、&str側で書き込みするとエラーになる。さっき「コピーされているという理解でいいのかな」とか書いたんだけど、間違っていたっぽい。
以下はコンパイルエラー。
let s7 : String = String::from("BBB"); let mut s8 : &str = &s7; s8.push_str("CCC");
(4) 文字列の配列
最後に文字列の配列。ざっくり行きます。
色々あるかもしれないけど今回はVecを使うやり方。Stringと同じくnewする必要がある。
let mut v1 : Vec<String> = Vec::new(); v1.push("XXXXX".to_string()); v1.push("YYYYY".to_string()); println!("{}", v1[0]); println!("{}", v1[1]); v1[1].push('Z'); println!("{}", v1[1]);
追加はpush()で、ただ""で囲んだ文字列はリテラルになるので、to_string()でStringに変換する必要あり。
読み取りはv[i]みたいに配列インデックスでアクセスできる。v[i]は指定した型(ここではString)の参照を返す(?)ようなので、(1)の方法で文字の追加も可能。
XXXXX YYYYY YYYYYZ
配列内の文字列の更に途中を参照したい場合、まずas_str()でStringを&strに変換して、あとは(2)に書いたようにget()で読み出せる。これも何か他にスマートな方法がありそう。
println!("{}", v1[1].as_str().get(0..3).unwrap());
YYY
以下のように、push()してない要素にインデックスでアクセスするとコンパイルエラー。
v1[2].push('Z');
これ解決策が分かっておらず、調べ中。取り合えず最初に空文字列をpush()して回避・・・
v1.push("".to_string()); v1[2].push('Z'); v1[2].push('Z'); println!("{}", v1[2]);
ZZ
(5) 追記:String型のループ
chars()で文字配列として取り出せる。
インデックスも合わせて参照したい場合はchars().enumerate()でインデックスと要素をセットで取り出せる。
let mut s9 : String = String::new(); s9.push_str("ABC"); for (i, c) in s9.chars().enumerate() { println!("{} {}", i, c); }
0 A 1 B 2 C
(6) 追記:末尾文字列の一致判定
Pythonと同じくends_withが使える。
if s9.ends_with("BC") == true { …
本日はここまで。
取り合えず作法として変でも、簡単な操作が実現できるところまで。The Bookも読んでるんだけど「これどうやるんだろ?」って思い始めると中々進まず。去年本読んでた頃から自分には荷が重いのは既に分かってるので、地道に続けてきます。