APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Rustの文字列操作まとめ

こんにちは、コンテナソリューション事業部の髙井です。
今日も前回に引き続きRustの記事を書いていこうと思います。

techblog.ap-com.co.jp

今回も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()でバイト列に変換して比較されるからです。

turreta.com

文字列の長さ

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");
}

おわりに

他にも頻出パターンがあると思うので、ちょくちょく書き足していこうと思います。

本記事の投稿者: 髙井 比文
AKSをメインにしたインフラとアプリの領域際をご支援することが多いです。Azureは11冠です。
Hifumi Takai - Credly