Удалите одну конечную новую строку из строки без клонирования

Я написал функцию для запроса ввода и возврата результата. В этой версии возвращаемая строка содержит завершающий перевод строки от пользователя. Я хотел бы вернуть ввод с удаленной новой строкой (и только с этой новой строкой):

fn read_with_prompt(prompt: &str) -> io::Result<String> {
    let stdout = io::stdout();
    let reader = io::stdin();
    let mut input = String::new();
    print!("{}", prompt);
    stdout.lock().flush().unwrap();
    reader.read_line(&mut input)?;

    // TODO: Remove trailing newline if present
    Ok(input)
}

Причиной удаления только одного завершающего символа новой строки является то, что эта функция также будет использоваться для запроса пароля (при соответствующем использовании termios для прекращения отображения), и если у кого-то из паролей есть конечные пробелы, это должно быть сохраненным.

После долгих раздумий о том, как на самом деле удалить одну новую строку в конце строки, я в итоге использовал trim_right_matches. Однако это возвращает &str. Я попытался использовать Cow для решения этой проблемы, но ошибка все равно говорит о том, что переменная input не живет достаточно долго.

fn read_with_prompt<'a>(prompt: &str) -> io::Result<Cow<'a, str>> {
    let stdout = io::stdout();
    let reader = io::stdin();
    let mut input = String::new();
    print!("{}", prompt);
    stdout.lock().flush().unwrap();
    reader.read_line(&mut input)?;

    let mut trimmed = false;
    Ok(Cow::Borrowed(input.trim_right_matches(|c| {
        if !trimmed && c == '\n' {
            trimmed = true;
            true
        }
        else {
            false
        }
    })))
}

Ошибка:

error[E0515]: cannot return value referencing local variable 'input'
  --> src/lib.rs:13:5
   |
13 |       Ok(Cow::Borrowed(input.trim_right_matches(|c| {
   |       ^                ----- 'input' is borrowed here
   |  _____|
   | |
14 | |         if !trimmed && c == '\n' {
15 | |             trimmed = true;
16 | |             true
...  |
20 | |         }
21 | |     })))
   | |________^ returns a value referencing data owned by the current function

Исходя из предыдущих вопросов по этим направлениям кажется, что это невозможно. Является ли единственная возможность выделить новую строку, для которой удален завершающий символ новой строки? Кажется, должен быть способ обрезать строку, не копируя ее (в C вы бы просто заменили '\n' на '\0').

Ответ 1

Вы можете использовать String::pop или String::truncate:

fn main() {
    let mut s = "hello\n".to_string();
    s.pop();
    assert_eq!("hello", &s);

    let mut s = "hello\n".to_string();
    let len = s.len();
    s.truncate(len - 1);
    assert_eq!("hello", &s);
}

Ответ 2

Более общее решение, чем принятое, которое работает с любым типом строки:

fn main() {
    let mut s = "hello\r\n".to_string();
    let len_withoutcrlf = s.trim_right().len();
    s.truncate(len_withoutcrlf);
    assert_eq!("hello", &s);
}

Ответ 3

Кроссплатформенный способ удаления одиночного завершающего символа новой строки без перераспределения строки таков:

fn trim_newline(s: &mut String) {
    if s.ends_with('\n') {
        s.pop();
        if s.ends_with('\r') {
            s.pop();
        }
    }
}

Это удалит "\n" или "\r\n" с конца строки, но без дополнительных пробелов.