2个不稳定版本
使用旧的Rust 2015
0.2.0 | 2017年3月17日 |
---|---|
0.1.0 | 2017年2月3日 |
在数据结构中排名第1834
每月下载量32次
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