String
Rust
开发者经常会被字符串困扰的原因Rust
倾向于暴露可能的错误- 字符串数据结构复杂
UTF8
字符串是什么
Byte
的集合- 一些方法
- 将
Byte
解析为文本
- 将
Rust
的核心语言层面,只有一个字符串类型,字符串切片str
(或&str
)- 字符串切片:对存储在其他地方、UTF8 编码的字符串的引用
- 字符串字面值:存储在二进制文件中,也是字符串切片
String 类型
- 来自标准库 而不是 核心语言
- 可增长、可修改、可获得所有权的类型
UTF8
编码
通常说的字符串是指
String 和 &str
- 在标准库里面用的多
- UTF8 编码
Rust 的标准库还包含了很多其他的字符串类型,例如 OsString、OsStr、CString、CStr
- String vs Str 后缀:可获得所有权 或 可借用的 变体
- 可存储不同编码的文本或在内存中的形式展现是不同的
Library crate 针对存储字符串可提供更多的选项
创建一个新的字符串
String
就是个 Byte
的集合
- 很多
Vec<T>
的操作都可用于String
。 - String::new() 函数
fn main(){
let mut s = String::new();
}
- 使用初始值来创建 String:
- to_string() 方法,可用于实现了 Display Trait 的类型,包括字符串字面值。
- String::from()函数,从字面值创建 String。
let data = "initial contents";
let s = data.to_string();
let s1 = "initial contents".to_string();
更新 String
它的大小是可以增减的,里面的内容也可以修改,它的操作就像操作 Vector 一样,此外还可以对 String 进行拼接。
- push_str() 方法:把一个字符串切片附加到 String(例子)。
fn main() {
let mut s = String::from("Hello");
let s1 = String::from(" World");
s.push_str(&s1);
println!("{}, {}", s, s1);
}
- push() 方法:把单个字符附加到 String
fn main() {
let mut s = String::from("Hello");
s.push(' ');
s.push('W');
s.push('o');
s.push('r');
s.push('l');
s.push('d');
println!("{}", s);
}
+ 拼接字符串
- 使用了类似这个签名的方法 fn add(self,:&str)-> String{...}
- 标准库中的 add 方法使用了泛型
- 只能把 &str 添加到 String
- 解引用强制转换(deref coercion)
fn main() {
let s1 = String::from("Hello");
let s2 = String::from(" World");
let s3 = s1 + &s2;
println!("s1:{},s2:{}, s3:{}", s1, s2, s3);
}
理解上文的字符串拼接
- 字符串拼接除去第一个,剩下的都必须是字符串切片的类型。
- deref coercion:它把 String 的引用转化成了字符串切片,所以编译就能通过,因为第二个字符串切皮有&符号,所以第二个参数的所有权就会保留,所以后续仍然可以使用。
- s1 没有用到 &符号 所以 add 函数就会取得第一个参数的所有权,所以在 s3 字符串拼接之后,s1 的所有权就交到了 add 函数里面,s1 从此就失效了,所以后面再使用 s1 就会报错。
- 所以这个字符串拼接的操作是取得了 s1 字符串的所有权,然后把 s2 的内容复制进来组成了一个新的结果并且把这个所有权赋予了 f3。
format!宏链接多个字符串
fn main() {
let s1 = String::from("ACE");
let s2 = String::from("Sabo");
let s3 = String::from("Luffy");
// let s3 = s1 + "-" + &s2 + "-" + &s3;
// println!("{}", s3);
let s = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s);
}
- 不会取得任何参数的所有权。
- 和 println 相似,但返回的是字符串。
对 String 按索引的形式进行访问
按索引语法访问
String
的某部分,会报错(例子)。Rust
的字符串不支持索引语法访问。内部表示
- String 是对
Vec<u8>
的包装。- len() 方法
- 每个 Unicode 标量值会占用 2 个字节
- String 是对
演示
fn main() {
let s1 = String::from("Здравствуйте").len();
let s = &s1[0];
println!("{}", s1);
}
当使用 UTF8 编码的时候 Здравствуйте 的第一个字母,它不是 3,就是很像 3 的字母,它实际上是占了两个字节,而这两个字节对应的值呢分别是 208 和 151,假如说这种语法是允许的, 我们就会得到 208 和 151,这个 208 并不是合法的字符,而且用户返回这个 208 也根本没有啥意义,但它确实是索引 0 上的字节,而且即便 208 对应的是合法字符,估计用户也不想得到的是 208, 所以为了避免这种意想不到的情况出现,并且防止引起无法立即发现的 bug,rust 语言就直接拒绝编译这段代码,也就是在开发的早期阶段,杜绝一切有可能产生的误解。
字节 标量值 字形簇
Bytes Scalar Values Grapheme Clusters
Rust 有三种看待字符串的方式:
- 字节
- 标量值
- 字形簇(最接近所谓的“字母”)
以字节的方式看待String
fn main() {
let s1 = String::from("नमस्ते"); // 梵文书写的印度单词
for b in s1.bytes() {
println!("{}", b);
}
}
以标量值的方式看待String
fn main() {
let s1 = String::from("नमस्ते"); // 梵文书写的印度单词
for b in s1.chars() {
println!("{}", b);
}
}
- Rust 不允许对 String 进行索引的最后一个原因:
- 索引操作应消耗
切割 String
- 可以使用 [] 和 一个 范围来创建字符串的切片
- 必须谨慎使用
- 如果切割时跨越字符边界,程序就会恐慌(panic)
注意
Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为
- 程序员必须在处理 UTF8 数据之前投入更多的精力
可防止在开发后期处理涉及非 ASCII 字符的错误。