#string #ez #garbage #collected #split #owned #ez-string

easy_strings

为Rust提供的 ergonomic、垃圾回收的字符串

2个不稳定版本

使用旧的Rust 2015

0.2.0 2017年3月17日
0.1.0 2017年2月3日

数据结构中排名第1834

每月下载量32

Apache-2.0/MIT

51KB
326

为Rust提供的 ergonomic、垃圾回收的字符串。

EZString类似于Python和Java等高级语言中的字符串。它被设计得尽可能容易使用,总是返回所有者值,通过底层使用引用计数和写时复制来提高效率。

入门

easy_strings可在crates.io上使用。将以下依赖项添加到您的Cargo清单中。

[dependencies]
easy_strings = "0.2"

然后在您的代码中导入它。

extern crate easy_strings;
use easy_strings::{EZString, ez};

创建

创建EZString最常见的方法是从字符串字面量创建,使用ez()辅助函数。它会将字符串进行内部化,因此多次以相同的字符串字面量调用它不会导致多个副本或分配。(但仍需要锁定和查询内部化字符串表。)

use easy_strings::{ez};
let s = ez("Hello, world!");

您还可以从现有的Strings或&strs创建EZString。

use easy_strings::{EZString};
let s = EZString::from("foo");
let s = EZString::from("foo".to_string());

连接

要连接字符串,请编写&a + &b。这种语法适用于a和b的类型,无论它们是EZString、&EZString、String、&String还是&str,只要a或b是EZString或&EZString即可。

let e = ez("E");
let re = &e;
let s = "s".to_string();
let rs = &s;
let lit = "lit";
assert_eq!(&e + &e, "EE");
assert_eq!(&e + &re, "EE");
assert_eq!(&e + &s, "Es");
assert_eq!(&e + &rs, "Es");
assert_eq!(&e + &lit, "Elit");
assert_eq!(&lit + &e, "litE");
assert_eq!(&lit + &re, "litE");
assert_eq!(&s + &re, "sE");
assert_eq!(&rs + &e, "sE");

注意:如果您正在使用Clippy,应使用#[allow(needless_borrow)],否则您将收到大量警告。

您也可以这样连接多个字符串,只要前两个中至少有一个是EZString或&EZString。

assert_eq!(&lit + &re + &s + &e + &e + &rs, "litEsEEs");

您还可以使用+=运算符。这被优化为只在左边的字符串不是唯一所有者时复制。这意味着以下循环是O(n)而不是O(n^2),而且不需要像Java中那样有单独的StringBuilder类型。

let mut s = ez("Some numbers: ");
for i in 0..5 {
    s += &i.to_string();
    s += &", ";
}
assert_eq!(s, "Some numbers: 0, 1, 2, 3, 4, ");

切片

切片是通过substr()方法完成的。请注意,索引是按字节进行的,而不是按代码点。如果提供的索引不在代码点边界上,substr()将崩溃。

let mut a = ez("Hello, world!");
assert_eq!(a.substr(1..), "ello, world!");
assert_eq!(a.substr(..6), "Hello,");
assert_eq!(a.substr(1..6), "ello,");
assert_eq!(a.substr(1..a.len()-1), "ello, world");

let b = a.substr(1..3);
a += &b; // b is a copy, so we can freely mutate a

substr()返回作为新EZString的子字符串。如果您想要借用切片而不是,请使用[]。这避免了额外的复制和分配,但代价是必须担心生命周期,而easy_strings正是为了避免这个问题而设计的。

let b = &a[1..3];
assert_eq!(b, "el");
// a += &b; // compile error because b borrowed a

等价性

多个EZString或& EZString之间的等价性测试工作正常。如果您想与String或& str进行比较,则EZString应该在左侧。如果它在右侧,则需要在其前面加上*(对于& EZString则为**)。

let e = ez("AAA");
let er = &e;
let s = String::from("AAA");
let sr = &s;
let lit = "AAA";
assert!(e == e);
assert!(er == er);
assert!(e == er);
assert!(er == e);
assert!(e == s);
assert!(e == sr);
assert!(e == lit);
assert!(er == s);
assert!(er == sr);
assert!(er == lit);
assert!(s == *e);
assert!(*sr == *e);
assert!(lit == *e);
assert!(s == **er);
assert!(*sr == **er);
assert!(lit == **er);

克隆

EZString不是Copy,这意味着每次您想按值重用时,都必须克隆它。为了解决这个问题,建议您的函数始终通过引用传递EZString参数,并返回拥有的EZString。这为调用者提供了最大的灵活性,并避免了在各个地方都需要调用clone()。EZString的自身方法,如这里的trim(),已经做到了这一点。

// bad: requires caller to clone() argument
fn foo(s: EZString) -> EZString { s.trim() }
// good
fn bar(s: &EZString) -> EZString { s.trim() }

话虽如此,有时按值取是不可避免的。在这种情况下,您需要克隆您的字符串。记住,这实际上并没有复制字符串,它只是增加了引用计数。

最简单、最标准的方式是调用.clone()。然而,如果您觉得这太啰嗦,也可以使用简写的.c()方法。c()还有一个优点,就是它始终克隆底层的EZString,即使您在嵌套引用上调用它(在这种情况下,clone()会克隆引用)。

let mut v: Vec<EZString> = Vec::new();
let s = ez("foo");
let rs = &s;
let rrs = &rs;

v.push(s.clone());
v.push(s.c());
v.push(rs.clone());
v.push(rs.c());
// v.push(rrs.clone()); // compile error
v.push(rrs.c());

强制转换

大多数库在String和& str上操作,而不是在EZString上。幸运的是,EZString可以解引用为& str,因此,在大多数情况下,您可以传入&s,并且它将正常工作。

fn take_str(_: &str) {}
let s = ez("");
let rs = &s;

take_str(&s);
take_str(&rs);

在复杂的情况下,例如与泛型函数一起,推理可能不起作用。在这种情况下,您可以通过as_str()显式获取& str。

take_str(s.as_str());
take_str(rs.as_str());

如果函数需要一个拥有的String,您可以使用to_string()方法。

fn take_string(_: String) {}
take_string(s.to_string());

字符串搜索

contains()、starts_with()、ends_with()、find()和rfind()方法都是通用的,这意味着如果您天真地传入一个EZString,您将得到一个令人困惑的编译错误。最容易的解决方案是使用上一节中提到的as_str()。或者,您可以为EZString编写&**,为& EZString编写&***。不需要特殊的语法来传递字面量。

let s = ez("Hello, world!");

assert!(s.contains("o, wo"));
assert!(s.starts_with("Hello"));
assert!(s.ends_with("world!"));
assert!(!s.ends_with("worl"));
assert_eq!(s.find("ld"), Some(10));
assert_eq!(s.find("l"), Some(2));
assert_eq!(s.rfind("l"), Some(10));

let p = ez("wor");
let r = &p;
assert!(s.contains(&*p));
assert!(s.contains(&**r));
assert!(s.contains(p.as_str()));
assert!(s.contains(r.as_str()));

请注意,find()和rfind()返回一个Option。要获得类似于Python的str.index()的行为,如果子字符串不存在则抛出异常,只需在结果上调用unwrap()。

assert_eq!(s.find("ld").unwrap(), 10);

字符串分割

您可以通过换行符、空白或提供的子字符串进行分割。返回的迭代器将结果包装在新EZString中。

let s = ez(" Hello,   world!\nLine two. ");

assert_eq!(s.lines().collect::<Vec<_>>(), vec![ez(" Hello,   world!"), ez("Line two. ")]);
assert_eq!(s.split_whitespace().collect::<Vec<_>>(),
           vec![ez("Hello,"), ez("world!"), ez("Line"), ez("two.")]);

let s = ez("aaa-bbb-ccc");
assert_eq!(s.split("-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb"), ez("ccc")]);
assert_eq!(s.rsplit("-").collect::<Vec<_>>(), vec![ez("ccc"), ez("bbb"), ez("aaa")]);

您还可以通过splitn()限制分割次数。

let s = ez("aaa-bbb-ccc");
assert_eq!(s.splitn(2, "-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb-ccc")]);
assert_eq!(s.rsplitn(2, "-").collect::<Vec<_>>(), vec![ez("ccc"), ez("aaa-bbb")]);

split_terminator()和rsplit_terminator()与split()/rsplit()相同,不同之处在于如果最后一个子字符串为空,则跳过它。这当字符串以分隔符终止而不是分隔时很有用。

let s = ez("aaa-bbb-");
assert_eq!(s.split("-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb"),  ez("")]);
assert_eq!(s.split_terminator("-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb")]);
assert_eq!(s.rsplit_terminator("-").collect::<Vec<_>>(), vec![ez("bbb"), ez("aaa")]);

let s = ez("aaa-bbb");
assert_eq!(s.split("-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb")]);
assert_eq!(s.split_terminator("-").collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb")]);

尽管迭代器是惰性的,但它们在创建时持有对字符串副本的引用。因此,如果您以后修改字符串,迭代结果不会改变。

let mut s = ez("aaa-bbb-ccc");
let it = s.split("-");
s += &"-ddd";
assert_eq!(it.collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb"), ez("ccc")]);
let it2 = s.split("-");
assert_eq!(it2.collect::<Vec<_>>(), vec![ez("aaa"), ez("bbb"), ez("ccc"), ez("ddd")]);

返回迭代器

每个迭代方法返回一个不同的类型。如果您想返回多个迭代器之一,您需要将它们装箱或立即评估。

例如,假设您想模拟Python的str.split()方法,该方法如果传入子字符串则按子字符串分割,如果没有传入参数则按空白分割。直接的方法不起作用,因为EZString::split()和EZString::split_whitespace()返回不同的类型。一种解决方案是立即评估它们,并返回一个字符串列表。

fn split<'a, P: Into<Option<&'a str>>>(s: &EZString, sep: P) -> Vec<EZString> {
    match sep.into() {
        Some(sep) => s.split(sep).collect(),
        None => s.split_whitespace().collect(),
    }
}

let s = ez("x  x-x 77x");
assert_eq!(split(&s, "x"), vec![ez(""), ez("  "), ez("-"), ez(" 77"), ez("")]);
assert_eq!(split(&s, None), vec![ez("x"), ez("x-x"), ez("77x")]);

或者,您可以装箱迭代器,从而保留惰性。

fn split<'a, P: Into<Option<&'a str>>>(s: &EZString, sep: P) -> Box<Iterator<Item=EZString>> {
    match sep.into() {
        Some(sep) => Box::new(s.split(sep)),
        None => Box::new(s.split_whitespace()),
    }
}

修剪

trim()、trim_left()和trim_right()方法从字符串的两端删除空白。

assert_eq!(ez("  hello \n ").trim(), "hello");
let s = ez("  hello \n ").trim_right();
assert_eq!(s, "  hello");
assert_eq!(s.trim_left(), "hello");

trim_left_matches()和trim_right_matches()从字符串两端删除指定的子字符串匹配项。请注意,与Python不同,它们不删除一组字符,而是删除一个子字符串。请注意,trim_matches()与其他所有方法不同。它接受一个字符而不是子字符串。

assert_eq!(ez("  hello   ").trim_matches(' '), "hello");
let s = ez(" x xhello x x x").trim_right_matches(" x");
assert_eq!(s, " x xhello");
assert_eq!(s.trim_left_matches(" x"), "hello");

字符串替换

您可以使用 .replace() 方法替换子字符串。

let s = ez("one fish two fish, old fish, new fish");
assert_eq!(s.replace("fish", "bush"), "one bush two bush, old bush, new bush");
assert_eq!(s.replace(&ez("fish"), &ez("bush")), "one bush two bush, old bush, new bush");

您也可以使用 .replacen() 方法替换前 n 次出现的子字符串。

# use easy_strings::*;
let s = ez("one fish two fish, old fish, new fish");
assert_eq!(s.replacen("fish", "bush", 3), "one bush two bush, old bush, new fish");
assert_eq!(s.replacen(&ez("fish"), &ez("bush"), 2), "one bush two bush, old fish, new fish");

其他方法

to_lowercase()、to_uppercase() 和 repeat() 方法基本上是自我解释的。

let s = ez("Hello, World!");
assert_eq!(s.to_lowercase(), "hello, world!");
assert_eq!(s.to_uppercase(), "HELLO, WORLD!");
assert_eq!(s.repeat(3), "Hello, World!Hello, World!Hello, World!");

请注意,to_lowercase 和 to_uppercase 方法是 Unicode 兼容的,但与区域无关。也就是说,无法获取 'i' 的土耳其语大写形式。

let s = ez("ὈΔΥΣΣΕΎΣ");
assert_eq!(s.to_lowercase(), "ὀδυσσεύς");

指针相等性

比较运算符 == 检查 相等,即给定的字符串是否包含相同的字节。如果您想测试两个 EZString 是否共享相同的底层缓冲区,可以使用 ptr_eq() 方法。请注意,由于 EZString 是写时复制(copy-on-write),共享缓冲区没有明显的效果,除了减少内存使用。因此,这种方法很少有用。

let a = ez("xxx");
let mut b = a.clone();
let c = &ez("xx") + &ez("x");
assert!(a.ptr_eq(&b));
assert!(b == c && !b.ptr_eq(&c));

b += &"foo";
// += is copy on write, so b no longer points to a
assert!(!a.ptr_eq(&b));
assert!(a == "xxx");
assert!(b == "xxxfoo");

依赖关系

~91KB