こんにちは、コンテナソリューション事業部の髙井です。
今日も前回に引き続きRustの記事を書いていこうと思います。
今回もRust初心者に鬼門の文字列にまつわる内容です。
インフラエンジニアなのに最近Rustの記事を書き始めたのは、今後ポスト・コンテナとして時代が到来しそうなWeb Assemblyへの道を拓くという個人的な企みがあるからです。
Rustの文字列操作まとめ
さて、前回の記事で所有権やスライスについては書いたので、そのあたりは把握している前提でまとめていきます。
今回も例によって比較用のPythonコードを併記します。
文字列の連結
Python:
s1 = "Hello" s2 = ", World!" connected = s1 + s2 assert connected == "Hello, World!"
Rust:
fn main() { let s1 = "Hello".to_string(); let s2 = ", World!"; let connected = s1 + s2; assert_eq!(connected, "Hello, World!"); }
ここでString
型に対して&str
で比較を行っていますが、これはエラーになりません。
assert_eq!
内ではPartialEq
トレイトの実装により、String
の参照に対してas_bytes()
でバイト列に変換して比較されるからです。
文字列の長さ
Python:
s = "Hello" n = len(s) assert n == 5
Rust:
fn main() { let s = "Hello"; let n = s.len(); assert_eq!(n, 5); }
N文字目を取得
Python:
s = "This blog entry is so good." n = s[5] assert n == "b"
Rust:
fn main() { let s = "This blog is so good."; let n = s.chars().nth(5).unwrap(); assert_eq!(n, 'b'); // `char` }
char
の配列にしてインデックスNを取得します。
あるいは、
fn main() { let s = "This blog is so good."; let n = s.get(5..6).unwrap(); assert_eq!(n, "b"); // `char`ではなく`&str` }
get()
でスライスを取得します。
スライスでインデックスを与えるため、文字列の長さによっては失敗する可能性があります。Rustではこうした場合にResult
型のような型でエラー可能性を明示してくれます。
今回は確実に取得できるインデックスだと知っているため、unwrap()
でResult
からノーチェックでchar
を取り出しています。
N文字目を書き換え
Python:
s = "ABCDDFG" s = list(s) s[4] = "E" s = "".join(s) assert s == "ABCDEFG"
Rubyなどと違いN文字目に直接アサインできないので一度リストにしてから書き換えてjoin()
して戻します。
あるいは、
s = "ABCDDFG" nth = 4 lhs = s[:nth] rhs = s[nth + 1 :] s = lhs + "E" + rhs assert s == "ABCDEFG"
N文字目を境に左右のスライスを取得してconcatします。
Rust:
RustもPythonと同様ですが、
fn main() { let s = "ABCDDFG"; let mut v: Vec<char> = s.chars().collect(); v[4] = 'E'; let s: String = v.iter().collect(); assert_eq!(s, "ABCDEFG"); }
chars()
が返すのはiteratorのため、collect()
でcollectionに変換します。今回は変更を伴うのでchar
のミュータブルな動的配列であるmut Vec<char>
とします。
fn main() { let s = "ABCDDFG"; let nth = 4; let lhs = s.get(..nth).unwrap(); let rhs = s.get((nth + 1)..).unwrap(); let s = lhs.to_string() + "E" + rhs; assert_eq!(s, "ABCDEFG"); }
こちらは先ほども使用したget()
を用いた例です。
fn main() { let mut s = "ABCDDFG".to_string(); s.remove(4); s.insert(4, 'E'); assert_eq!(s, "ABCDEFG"); }
N文字目を削除するremove()
とN文字目に挿入するinsert()
を用いることもできます。
char
ではなく&str
を挿入するinsert_str()
もあります。
数値文字列を数値に変換
Python:
s = "123456" n = int(s) assert n == 123456
Rust:
fn main() { let s = "123456"; let n: i32 = s.parse().unwrap(); assert_eq!(n, 123456); }
Rustではparse()
を使います。明示的な型指定が必要な点に注意してください。
型指定は以下のように書くこともできます。
let n = s.parse::<i32>().unwrap();
N回繰り返す
Python:
s = "abc" s *= 3 assert s == "abcabcabc"
Rust:
fn main() { let s = "abc"; let s = s.repeat(3); assert_eq!(s, "abcabcabc"); }
前後の空白を取り除く
Python:
s = "\t\t\tSPACES!\n\n\n\t\t\t" s = s.strip() assert s == "SPACES!"
Rust:
fn main() { let s = "\t\t\tSPACES!\n\n\n\t\t\t"; let s = s.trim(); assert_eq!(s, "SPACES!"); }
前だけのlstrip()
とtrim_start()
、後ろだけのrstrip()
とtrim_end()
もあります。
正規表現を使う
Python:
import re pat = re.compile("[0-9]+") s = pat.sub("", "ni991ce regex") assert s == "nice regex"
Rust:
Rustでは正規表現は言語標準に組み込まれていないのでcargo.toml
に追記します。
[dependencies] regex = "0.1"
以下のようにインポートして使います。
extern crate regex; use regex::Regex; fn main() { let s = "ni991ce regex"; let pat = Regex::new(r"[0-9]+").unwrap(); let s = pat.replace(s, ""); assert_eq!(s, "nice regex"); }
おわりに
他にも頻出パターンがあると思うので、ちょくちょく書き足していこうと思います。