#string #rust #unsafe
#Ржавчина
Вопрос:
Я написал функцию для titlecase (первая буква заглавная, все остальные строчные) заимствованной строки, но в итоге это оказалось сложнее, чем должно быть.
fn titlecase_word(word: amp;mut String) {
unsafe {
let buffer = word.as_mut_vec().as_mut_slice();
buffer[0] = std::char::to_uppercase(buffer[0] as char) as u8;
for i in range(1, buffer.len()) {
buffer[i] = std::char::to_lowercase(buffer[i] as char) as u8;
}
}
}
Небезопасный блок особенно нежелателен. Есть ли более удобный способ изменить содержимое строки по индексу?
Ответ №1:
Обновление: обновлено для последней версии Rust. Начиная с версии Rust 1.0.0-alpha, to_lowercase()
/ to_uppercase()
теперь являются методами в CharExt
черте, и отдельного Ascii
типа больше нет: операции ASCII теперь собраны в двух чертах, AsciiExt
и OwnedAsciiExt
. Они помечены как нестабильные, поэтому, вероятно, могут меняться в течение бета-тестирования Rust.
Ваш код неверен, потому что он обращается к отдельным байтам для выполнения операций на основе символов, но в UTF-8 символы не являются байтами. Это не будет работать корректно для всего, что не является ASCII.
На самом деле, нет способа сделать это правильно на месте, потому что любые преобразования символов могут изменить количество байтов, занимаемых символом, и это потребует полного перераспределения строки. Вы должны перебирать символы и собирать их в новую строку:
fn titlecase_word(word: amp;mut String) {
if word.is_empty() { return; }
let mut result = String::with_capacity(word.len());
{
let mut chars = word.chars();
result.push(chars.next().unwrap().to_uppercase());
for c in chars {
result.push(c.to_lowercase());
}
}
*word = resu<
}
(попробуйте это здесь)
Поскольку вам все равно нужно сгенерировать новую строку, лучше просто вернуть ее, не заменяя старую. В этом случае также лучше передать фрагмент функции:
fn titlecase_word(word: amp;str) -> String {
let mut result = String::with_capacity(word.len());
if !word.is_empty() {
let mut chars = word.chars();
result.push(chars.next().unwrap().to_uppercase());
for c in chars {
result.push(c.to_lowercase());
}
}
result
}
(попробуйте это здесь)
Также String
имеет extend()
метод из Extend
trait, который обеспечивает более идиоматический подход в отличие от for
цикла:
fn titlecase_word(word: amp;str) -> String {
let mut result = String::with_capacity(word.len());
if !word.is_empty() {
let mut chars = word.chars();
result.push(chars.next().unwrap().to_uppercase());
result.extend(chars.map(|c| c.to_lowercase()));
}
result
}
(попробуйте это здесь)
Фактически, с помощью итераторов можно сократить его еще больше:
fn titlecase_word(word: amp;str) -> String {
word.chars().enumerate()
.map(|(i, c)| if i == 0 { c.to_uppercase() } else { c.to_lowercase() })
.collect()
}
(попробуйте это здесь)
Однако, если вы заранее знаете, что работаете с ASCII, вы можете использовать признаки, предоставляемые std::ascii
модулем:
fn titlecase_word(word: String) -> String {
use std::ascii::{AsciiExt, OwnedAsciiExt};
assert!(word.is_ascii());
let mut result = word.into_bytes().into_ascii_lowercase();
result[0] = result[0].to_ascii_uppercase();
String::from_utf8(result).unwrap()
}
(попробуйте это здесь)
Эта функция завершится ошибкой, если входная строка содержит какой-либо символ, отличный от ASCII.
Эта функция ничего не выделит и изменит содержимое строки на месте. Однако вы не можете написать такую функцию с одним amp;mut String
аргументом без unsafe и без дополнительных выделений, потому что для этого потребуется выйти из amp;mut
, а это запрещено.
Однако вы можете использовать std::mem::swap()
временную переменную and с пустой строкой — это не потребует unsafe, но может потребовать выделения пустой строки. Я не помню, действительно ли это требует выделения; если нет, то вы можете написать такую функцию, хотя код будет несколько громоздким. В любом случае, amp;mut
-аргументы на самом деле не являются идиоматическими для Rust.
Комментарии:
1. Спасибо за тщательность.
2. @Vladimir: Пустое
String
значение поддерживается пустымVec
, которое не выделяет место в куче .3.
result.push(c.to_lowercase());
не компилируется с Rust 1.9, так какto_lowercase()
теперь возвращает astd::char::ToLowercase
.