오래된 C++ 코드를 Rust로 옮기고 있는데, std::string을 바이트 한도에 맞춰 s.substr(0, n)으로 자르는 부분이 있어요. 한 줄이면 될 줄 알았죠. C++에서는 그럭저럭 돌아갔어요. std::string은 UTF-8을 전혀 모르니까 글자를 반으로 잘라도 그냥 넘어가고, 키릴 문자는 한 글자가 2바이트라 한도가 홀수면 끝이 깨졌는데, 그 바이트를 나중에 디코딩하는 곳이 없었는지 아무도 몰랐어요. 그런데 Rust는 첫 러시아어 이름에서 &s[..n]을 하다가 byte index 5 is not a char boundary로 죽었고, 거기서부터 전부 의심스러워졌어요.
방법은 몇 가지 보여요. n에서 거꾸로 가면서 s.is_char_boundary(i)가 참이 될 때까지 내려가는 것. UTF-8 문자는 4바이트를 넘지 않으니까 많아야 세 번이에요. 아니면 char_indices()로 돌면서 문자가 통째로 들어가는 마지막 위치를 잡는 것. 그리고 만약 이 한도가 사실 바이트가 아니라 사람이 보는 글자 기준이라면 둘 다 틀린 거고, 솔직히 이 한도가 애초에 어디서 왔는지도 모르겠어요. 아마 DB 컬럼 크기였던 것 같은데, 그렇다면 세는 일도 DB가 해야 하는 거 아닐까 싶기도 하고요.
일단은 루프로 가요.
fn cut(s: &str, n: usize) -> &str {
if n >= s.len() {
return s;
}
let mut i = n;
while !s.is_char_boundary(i) {
i -= 1;
}
&s[..i]
}
cut("Привет", 5)는 4바이트짜리 Пр를 돌려주고, 한도가 0이면 panic 대신 빈 문자열이 나와요. 줄마다 뭘 하는지 다 말할 수 있을 만큼 짧은데, 지금 저한테는 그게 영리한 것보다 더 중요해요.