Rustの文字列型にはreplaceというメソッドが実装されており、部分文字列の置換を行うことが出来る。
しかし、この関数は元の文字列を変更するのではなく、新たな文字列を生成するようだ。
この文字列の生成が無駄なように感じたので、Rustの練習も兼ねて元の文字列を書き換える関数を実装した。
実装
unsafe fn replace_inplace(s: &mut String, from: u8, to: u8) {
let sv = s.as_bytes_mut();
sv.iter_mut().filter(|x| **x == from).for_each(|x| *x = to);
}
使用方法
let mut s = String::from("a\\b\\c");
unsafe { replace_inplace(&mut s, b'\\', b'/'); }
println!("{}", s); // => "a/b/c"
ポイント
str
型はas_bytes_mut
メソッドを実装しており、このメソッドによってミュータブルなバイト列を取得できる。
let sv = s.as_bytes_mut();
しかし、これによって得られたバイト列を使えば、元の文字列を書き換えることが出来てしまう。しかももとの変数は借用されていないので、この関数を呼び出した後は、同じアドレスへの書き込み権限を有する変数が同時に2つ存在することになる。
したがって、いずれかの変数のライフタイムが切れるまではこのコードはunsafeであり、データ競合が起こらないように注意しなければならない。
例えば元の文字列を変更しようとした時に、メモリの再確保が行われるかもしれない。
// 文字列の長さを増やしているので、領域が足りない場合はメモリの再確保が行われる
s += "/d/e"
このとき、文字列の内部のデータのアドレスも新しいアドレスに置き換わるので、svが指すアドレスはもはや有効なアドレスではなくなる。またこの逆のパターン(取得したバイト列のメモリの再確保)もあるので注意しよう。
追記 (2019/7/16)
- 変数の借用についての記述を修正
- この実装だとUTF-8シークエンスを破壊する可能性があるので、関数自体をunsafeに変更
おわりに
今回は1文字だけの置換だったが、もっと複雑な置換も実装してみたいと思う。