Переношу на Rust кусок старого C++, который обрезает std::string до лимита в байтах обычным s.substr(0, n), и думал, что это одна строчка. В C++ оно как бы работало, хотя std::string про UTF-8 ничего не знает и спокойно отрезает полбуквы, а в кириллице каждая буква - два байта, так что при нечётном лимите хвост получался битым, и этого никто не замечал, потому что, похоже, дальше эти байты никто не декодировал. А Rust на первом же русском имени сделал &s[..n] и упал с byte index 5 is not a char boundary, и тут я начал сомневаться вообще во всём.
Вариантов вижу несколько. Можно идти назад от n, пока s.is_char_boundary(i) не вернёт true, это максимум три шага, потому что символ в UTF-8 не длиннее четырёх байт. Можно пройти char_indices() и взять последнюю позицию, где символ ещё влезает целиком. А если лимит на самом деле не про байты, а про то, что видит человек, тогда неправильно и то и другое, и я, честно говоря, не знаю, откуда этот лимит вообще взялся, кажется, из ширины колонки в базе, а если из базы, то, может, и считать должна сама база.
Пока что цикл.
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) даёт Пр, четыре байта, а нулевой лимит даёт пустую строку вместо паники. Он такой короткий, что я могу объяснить каждую строчку, и сейчас для меня это важнее, чем быть хитрым.