Есть ли такой метод, как JavaScript substr в Rust?

Я просмотрел документы Rust для String но не могу найти способ извлечь подстроку.

Есть ли такой метод, как JavaScript substr в Rust? Если нет, как бы вы это реализовали?

str.substr(start[, length])

Наиболее близким является, вероятно, slice_unchecked но он использует смещения байтов вместо символьных индексов и помечен как unsafe.

Ответ 1

Для символов вы можете использовать s.chars().skip(pos).take(len):

fn main() {
    let s = "Hello, world!";
    let ss: String = s.chars().skip(7).take(5).collect();
    println!("{}", ss);
}

Остерегайтесь определения символов Unicode.

Для байтов вы можете использовать синтаксис среза:

fn main() {
    let s = "Hello, world!";
    let ss = &s[7..12];
    println!("{}", ss);
}

Ответ 2

Вы можете использовать метод as_str на итераторе Chars, чтобы вернуть &str после того, как вы наступили на итератор. Итак, чтобы пропустить первые символы start, вы можете позвонить

let s = "Some text to slice into";
let mut iter = s.chars();
iter.by_ref().nth(start); // eat up start values
let slice = iter.as_str(); // get back a slice of the rest of the iterator

Теперь, если вы также хотите ограничить длину, сначала вам нужно определить байтовую позицию символа length:

let end_pos = slice.char_indices().nth(length).map(|(n, _)| n).unwrap_or(0);
let substr = &slice[..end_pos];

Это может показаться немного крутым, но Rust не скрывает от вас ничего, что могло бы занять циклы процессора. Тем не менее, я задаюсь вопросом, почему еще нет ящика, который предлагает метод substr.

Ответ 3

Для синтаксиса my_string.substring(start, len) -like вы можете написать пользовательский признак:

trait StringUtils {
    fn substring(&self, start: usize, len: usize) -> Self;
}

impl StringUtils for String {
    fn substring(&self, start: usize, len: usize) -> Self {
        self.chars().skip(start).take(len).collect()
    }
}

// Usage:
fn main() {
    let phrase: String = "this is a string".to_string();
    println!("{}", phrase.substring(5, 8)); // prints "is a str"
}

Ответ 4

Этот код выполняет как подстроки, так и срезы строк, без паники и выделения:

use std::ops::{Bound, RangeBounds};

trait StringUtils {
    fn substring(&self, start: usize, len: usize) -> &str;
    fn slice(&self, range: impl RangeBounds<usize>) -> &str;
}

impl StringUtils for str {
    fn substring(&self, start: usize, len: usize) -> &str {
        let mut char_pos = 0;
        let mut byte_start = 0;
        let mut it = self.chars();
        loop {
            if char_pos == start { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_start += c.len_utf8();
            }
            else { break; }
        }
        char_pos = 0;
        let mut byte_end = byte_start;
        loop {
            if char_pos == len { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_end += c.len_utf8();
            }
            else { break; }
        }
        &self[byte_start..byte_end]
    }
    fn slice(&self, range: impl RangeBounds<usize>) -> &str {
        let start = match range.start_bound() {
            Bound::Included(bound) | Bound::Excluded(bound) => *bound,
            Bound::Unbounded => 0,
        };
        let len = match range.end_bound() {
            Bound::Included(bound) => *bound + 1,
            Bound::Excluded(bound) => *bound,
            Bound::Unbounded => self.len(),
        } - start;
        self.substring(start, len)
    }
}

fn main() {
    let s = "abcdèfghij";
    // All three statements should print:
    // "abcdè, abcdèfghij, dèfgh, dèfghij."
    println!("{}, {}, {}, {}.",
        s.substring(0, 5),
        s.substring(0, 50),
        s.substring(3, 5),
        s.substring(3, 50));
    println!("{}, {}, {}, {}.",
        s.slice(..5),
        s.slice(..50),
        s.slice(3..8),
        s.slice(3..));
    println!("{}, {}, {}, {}.",
        s.slice(..=4),
        s.slice(..=49),
        s.slice(3..=7),
        s.slice(3..));
}