在Rust中有像JavaScript的substr这样的方法吗?

时间:2016-05-11 09:09:00

标签: string substring rust

我查看了Rust docs for String,但我找不到提取子字符串的方法。

在Rust中是否存在类似JavaScript的substr的方法?如果没有,你会如何实现它?

str.substr(start[, length])

最接近的可能是slice_unchecked,但它使用字节偏移而不是字符索引,并标记为unsafe

7 个答案:

答案 0 :(得分:28)

对于字符,您可以使用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);
}

答案 1 :(得分:10)

您可以使用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并没有隐藏任何可能占用CPU周期的东西。也就是说,我想知道为什么还没有提供substr方法的箱子。

答案 2 :(得分:3)

对于my_string.substring(start, len) - 类似语法,您可以编写自定义特征:

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

答案 3 :(得分:1)

oli_obk 给出的解决方案没有处理字符串切片的最后一个索引。它可以通过 .chain(once(s.len())) 修复。

这里的函数 substr 实现了一个带有错误处理的子字符串切片。如果将无效索引传递给函数,则字符串切片的有效部分将返回 Err-variant。应正确处理所有极端情况。

fn substr(s: &str, begin: usize, length: Option<usize>) -> Result<&str, &str> {
    use std::iter::once;
    let mut itr = s.char_indices().map(|(n, _)| n).chain(once(s.len()));
    let beg = itr.nth(begin);
    if beg.is_none() {
        return Err("");
    } else if length == Some(0) {
        return Ok("");
    }
    let end = length.map_or(Some(s.len()), |l| itr.nth(l-1));
    if let Some(end) = end {
        return Ok(&s[beg.unwrap()..end]);
    } else {
        return Err(&s[beg.unwrap()..s.len()]);
    }
}
let s = "abc?";
assert_eq!(Ok("bc"), substr(s, 1, Some(2)));
assert_eq!(Ok("c?"), substr(s, 2, Some(2)));
assert_eq!(Ok("c?"), substr(s, 2, None));
assert_eq!(Err("c?"), substr(s, 2, Some(99)));
assert_eq!(Ok(""), substr(s, 2, Some(0)));
assert_eq!(Err(""), substr(s, 5, Some(4)));

请注意,这不处理 unicode 字形簇。例如,"y̆es" 包含 4 个 unicode char,但包含 3 个字素簇。 Crate unicode-segmentation 解决了这个问题。如果部分

Unicode 字素簇得到正确处理
let mut itr = s.char_indices()...

被替换为

use unicode_segmentation::UnicodeSegmentation;
let mut itr = s.grapheme_indices(true)...

然后还有以下作品

assert_eq!(Ok("y̆"), substr("y̆es", 0, Some(1)));

答案 4 :(得分:0)

此代码执行子字符串切片和字符串切片,而不会惊慌或分配:

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

答案 5 :(得分:0)

您也可以使用.to_string()[ <range> ]

此示例获取原始字符串的不变切片,然后对该字符串进行突变以证明原始切片被保留。

let mut s: String = "Hello, world!".to_string();

let substring: &str = &s.to_string()[..6];

s.replace_range(..6, "Goodbye,");

println!("{}   {} universe!", s, substring);

//    Goodbye, world!   Hello, universe!

答案 6 :(得分:0)

我建议您使用板条箱 substring。 (如果您想了解如何正确执行此操作,请查看 its source code。)