简体   繁体   中英

How to “crop” characters off the beginning of a string in Rust?

I want a function that can take two arguments (string, number of letters to crop off front) and return the same string except with the letters before character x gone.

If I write

let mut example = "stringofletters";
CropLetters(example, 3);
println!("{}", example);

then the output should be:

ingofletters

Is there any way I can do this?

In many uses it would make sense to simply return a slice of the input, avoiding any copy. Converting @Shepmaster's solution to use immutable slices:

fn crop_letters(s: &str, pos: usize) -> &str {
    match s.char_indices().skip(pos).next() {
        Some((pos, _)) => &s[pos..],
        None => "",
    }
}

fn main() {
    let example = "stringofletters"; // works with a String if you take a reference
    let cropped = crop_letters(example, 3);
    println!("{}", cropped);
}

Advantages over the mutating version are:

  • No copy is needed. You can call cropped.to_string() if you want a newly allocated result; but you don't have to.
  • It works with static string slices as well as mutable String etc.

The disadvantage is that if you really do have a mutable string you want to modify, it would be slightly less efficient as you'd need to allocate a new String .

Issues with your original code:

  1. Functions use snake_case , types and traits use CamelCase .
  2. "foo" is a string literal of type &str . These may not be changed. You will need something that has been heap-allocated, such as a String .
  3. The call crop_letters(stringofletters, 3) would transfer ownership of stringofletters to the method, which means you wouldn't be able to use the variable anymore. You must pass in a mutable reference ( &mut ).
  4. Rust strings are not ASCII , they are UTF-8 . You need to figure out how many bytes each character requires. char_indices is a good tool here.
  5. You need to handle the case of when the string is shorter than 3 characters.
  6. Once you have the byte position of the new beginning of the string, you can use drain to move a chunk of bytes out of the string. We just drop these bytes and let the String move over the remaining bytes.
fn crop_letters(s: &mut String, pos: usize) {
    match s.char_indices().nth(pos) {
        Some((pos, _)) => {
            s.drain(..pos);
        }
        None => {
            s.clear();
        }
    }
}

fn main() {
    let mut example = String::from("stringofletters");
    crop_letters(&mut example, 3);
    assert_eq!("ingofletters", example);
}

See Chris Emerson's answer if you don't actually need to modify the original String .

I found this answer which I don't consider really idiomatic:

fn crop_with_allocation(string: &str, len: usize) -> String {
    string.chars().skip(len).collect()
}

fn crop_without_allocation(string: &str, len: usize) -> &str {
    // optional length check
    if string.len() < len {
        return &"";
    }
    &string[len..]
}

fn main() {
    let example = "stringofletters"; // works with a String if you take a reference
    let cropped = crop_with_allocation(example, 3);
    println!("{}", cropped);
    let cropped = crop_without_allocation(example, 3);
    println!("{}", cropped);
}

my version

fn crop_str(s: &str, n: usize) -> &str {
    let mut it = s.chars();
    for _ in 0..n {
        it.next();
    }
    it.as_str()
}

#[test]
fn test_crop_str() {
    assert_eq!(crop_str("123", 1), "23");
    assert_eq!(crop_str("ЖФ1", 1), "Ф1");
    assert_eq!(crop_str("ЖФ1", 2), "1");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM