2025/05/27
はじめに
Rustでコードを書いていて、String
の行分割をした時に、特に意識していなかった挙動で苦労したことがあったのでメモを残す。
発端
String
には、.lines()
という、文字列を行に分割するために便利なメソッドがある。
ファイルを読み込んで行ごとに処理する際、何気なくこのメソッドでVec<String>
に変換して使ったところ、最後の空行が省略されることに気づいた。
そこで、最後の空行も取得したい場合の使い方および、それらの使い分けについて挙動を確認していく。
.lines()の挙動
.lines()
は、最後の空行は省略される。
let content = r#"
fn main() {
}
"#;
assert_eq!(
content.lines().collect::<Vec<&str>>(),
vec!["", "fn main() {", "}"]
);
.split()の挙動
上の入力を与える際に、最後の空行も含めて取得したい場合は、.split()
を使う。
.split()
は、改行文字で分割するにとどまり、最後の空行も取得できる。
let content = r#"
fn main() {
}
"#;
assert_eq!(
content.split("\n").collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", ""]
);
.lines()と.split()の使い分け
上の入力に加え、いくつかの末尾の状況に対する動作を列挙する。
{ // 末尾に空行がひとつある場合
let content = r#"
fn main() {
}
"#;
assert_eq!(
content.lines().collect::<Vec<&str>>(),
vec!["", "fn main() {", "}"]
);
assert_eq!yamlやtoml(
content.split("\n").collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", ""]
);
}
{ // 末尾に空行がふたつある場合
let content = r#"
fn main() {
}
"#;
assert_eq!(
content.lines().collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", ""]
);
assert_eq!(
content.split("\n").collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", "", ""]
);
}
{ // 末尾にスペースのみの行がある場合
let content = r#"
fn main() {
}
"#;
assert_eq!(
content.lines().collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", " "]
);
assert_eq!(
content.split("\n").collect::<Vec<&str>>(),
vec!["", "fn main() {", "}", " "]
);
}
おわりに
この動作は、yamlやtomlのパースなど、多くの場合問題は生じないが、「ファイルを読み込んで行に対して何らかの処理をして元のファイルに書き戻す」ような場合には、意図しない挙動を引き起こす可能性がある。