NF

地方で働くプログラマ

らすとのもじれつ

最近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も読んでるんだけど「これどうやるんだろ?」って思い始めると中々進まず。去年本読んでた頃から自分には荷が重いのは既に分かってるので、地道に続けてきます。