Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove single trailing newline from String without cloning

Tags:

string

rust

I have written a function to prompt for input and return the result. In this version the returned string includes a trailing newline from the user. I would like to return the input with that newline (and just that newline) removed:

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)
}

The reason for only removing the single trailing newline is that this function will also be used to prompt for a password (with appropriate use of termios to stop echoing) and if someone's password has trailing whitespace this should be preserved.

After much fussing about how to actually remove a single newline at the end of a string I ended up using trim_right_matches. However that returns a &str. I tried using Cow to deal with this but the error still says that the input variable doesn't live long enough.

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:

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

Based on previous questions along these lines it seems this is not possible. Is the only option to allocate a new string that has the trailing newline removed? It seems there should be a way to trim the string without copying it (in C you'd just replace the '\n' with '\0').

like image 206
Wes Avatar asked Jun 17 '16 18:06

Wes


People also ask

How do you get rid of trailing new lines?

The canonical way to strip end-of-line (EOL) characters is to use the string rstrip() method removing any trailing \r or \n. Here are examples for Mac, Windows, and Unix EOL characters. Using '\r\n' as the parameter to rstrip means that it will strip out any trailing combination of '\r' or '\n'.

Does Rstrip remove newline?

The rstrip() method removes any trailing character at the end of the string. By using this method, we can remove newlines in the provided string value.

What is a trailing newline Python?

A trailing new line is a '\n' character at the end of a line like that in Python strings.


3 Answers

You can use String::pop or 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); } 
like image 127
malbarbo Avatar answered Sep 22 '22 05:09

malbarbo


A cross-platform way of stripping a single trailing newline without reallocating the string is this:

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

This will strip either "\n" or "\r\n" from the end of the string, but no additional whitespace.

like image 23
Sven Marnach Avatar answered Sep 21 '22 05:09

Sven Marnach


with strip_suffix

This removes one trailing \r\n or \n:

fn strip_trailing_newline(input: &str) -> &str {
    input
        .strip_suffix("\r\n")
        .or(input.strip_suffix("\n"))
        .unwrap_or(input)
}

If there are multiple newlines, only the last one is stripped off.

If there is no newline at the end of the string, the string is unchanged.

Some tests:

#[test]
fn strip_newline_works(){
    assert_eq!(strip_trailing_newline("Test0\r\n\r\n"), "Test0\r\n");
    assert_eq!(strip_trailing_newline("Test1\r\n"), "Test1");
    assert_eq!(strip_trailing_newline("Test2\n"), "Test2");
    assert_eq!(strip_trailing_newline("Test3"), "Test3");
}
like image 36
Matthias Braun Avatar answered Sep 22 '22 05:09

Matthias Braun