Rust 3.1 字符串2
31 Jan 2015 by LelouchHe
字符串是任何语言中一个重要的概念.如果你有其他语言背景,你会对系统语言中字符串处理的复杂性感到吃惊.高效存取和动态内存分配涉及很多细节.幸运的是,Rust提供了一些辅助工具.
字符串(string)是UTF-8编码的unicode值序列.所有的字符串都是正确编码的UTF-8序列.另外,字符串不是以’\0’结尾,中间可以包含’\0’字符.
Rust有2种主要的字符串类型: &str
和String
.
&str
第一种类型是&str
,称为”字符串切片”(string slice).字符串字面值(string literal)就是&str
类型:
let string = "Hello there.";
就像其他Rust值一样,&str
有自己的生命期.一个字符串字面值的类型是&'static str
.很多场景下,&str
可以不用像函数参数一样,显式的写出生命期.这些场景下,生命期会被自动推断出来:
fn takes_slice(slice: &str) {
println!("Got: {}", slice);
}
就像vector slice一样,&str
就是简单的一个指针加一个长度.这意味着它们是一个已经分配好内存的字符串的视图(view),比如说是字符串字面值,或一个String
.
String
String
是在堆上分配的字符串类型.这种字符串可以自动增长,也是UTF-8编码的.
let mut s = "Hello".to_string();
println!("{}", s);
s.push_str(", world.");
println!("{}", s);
你还可以通过解引用把String
转型为&str
:
fn takes_slice(slice: &str) {
println!("Got: {}", slice);
}
fn main() {
let s = "Hello".to_string();
takes_slice(&*s);
}
也可以把栈上的字符数组转型为&str
:
use std::str;
let x: &[u8] = &[b'a', b'b'];
let stack_str: &str = str::from_utf8(x).unwrap()
最佳实践
String和&str
通常来说,当你需要拥有字符串时,使用String
,而当你只需要借用字符串时,使用&str
.很类似Vec<T>
和&[T]
的使用情况,此处T
指代类型.
这意味着,如果你有一个很好的原因的话,可以从这样:
fn foo(s: &str) {}
修改成这样:
fn foo(s: String) {}
取得你不需要的变量的所有权,不是好的编码实践,这让你的生命期管理变得愈发复杂.
通用函数
如果要写一个接受字符串参数的通用函数,使用&str
:
fn some_string_length(x: &str) -> usize {
x.len
}
fn main() {
let s = "Hello world";
println!("{}", some_string_length(s));
let s = "Hello, world".to_string();
println!("{}", some_string_length(s.as_slice()));
}
这2种调用都会输出12
.
字符串下标
你也许会像下面这样获取String
中的某个字符:
let s = "hello".to_string();
println!("{}", s[0]);
但这个无法通过编译.这是专门设计成这样的.处理UTF-8字符串时,直接的下标索引基本上是有问题的.原因在于UTF-8字符可能由不同数量的字节构成.这意味着你必须遍历整个字符串才能完成这个操作,其时间复杂度是O(n)
.
unicode有3种基本类型(还有它的编码):
- code unit, 这是底层用来存储数据的类型
- code point或unicode scalar value (译者: 这个就是通常的
char
) - grapheme, 可见的字符型式
Rust对每种类型都提供了对应的迭代器:
.bytes()
, 遍历底层的字节.chars()
, 遍历所有的char.graphemes()
, 遍历所有的可见字符(译者: chars是字符值,这个则是可见的那个自负形式)
通常,&str
上的graphemes
就是你想要的:
let s = "u?n???i????c??o????d????e?"
for l in s.graphemes(true) {
println!("{}", l);
}
这个会输出:
u?
n???
i????
c??
o????
d????
e?
注意,l
的类型是&str
.由于每个可见字符都有多个code point构成,所以char
是不对的.(译者: 这里是说,上面的每个可见字符都有主字符和上下缀字符构成,这些都各自需要一个code point/char来表示,所以我们这里只能用graphemes,而不能用chars)
这个就能如你所愿的输出每个可见字符:先是”u?”,然后是”n???”等等.如果你想输出每个可见字符的codepoint,你可以利用chars()
:
let s = "u?n???i????c??o????d????e?"
for l in s.chars(true) {
println!("{}", l);
}
这个会输出:
u
?
n
?
?
?
i
?
?
?
?
c
?
?
o
?
?
?
?
d
?
?
?
?
e
?
你可以看到其中一些是多个char的组合,所以输出有些怪异.
如果你想要输出每个codepoint的字节表示,可以使用bytes()
:
let s = "u?n???i????c??o????d????e?"
for l in s.bytes(true) {
println!("{}", l);
}
这个会输出:
117
205
148
110
204
142
205
136
204
176
105
204
153
204
174
205
154
204
166
99
204
137
205
154
111
205
151
204
188
204
169
204
176
100
204
134
205
131
205
165
205
148
101
204
129
会输出比可见字符更多的底层字节!