常见集合¶
Vec¶
新建 vector¶
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];
更新 vector¶
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
如果想要能够改变它的值,必须使用 mut
关键字使其可变。
读取 vector 的元素¶
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2]; // 使用索引
println!("The third element is {third}"); // 使用get方法
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
&
和 []
会得到一个索引位置元素的引用。当使用索引作为参数调用 get
方法时,会得到一个可以用于 match
的 Option<&T>
。
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0]; // 引用
v.push(6); // 错误!
println!("The first element is: {first}");
遍历 vector¶
let v = vec![100, 32, 57];
for i in &v {
println!("{i}");
}
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
+=
运算符之前必须使用解引用运算符(*
)获取 i
中的值。
因为借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全的。
但是如果想在for
循环时插入元素,则会产生错误
使用枚举来存储多种类型¶
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。
丢弃 vector 时也会丢弃其所有元素
{
let v = vec![1, 2, 3, 4];
// do stuff with v
} // <- v goes out of scope and is freed here
String¶
Rust 的核心语言中只有一种字符串类型:字符串 slice str
,它通常以被借用的形式出现,&str
。由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。
新建字符串¶
很多 Vec
可用的操作在 String
中同样可用,事实上 String
被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 Vec<T>
和 String
函数的例子是用来新建一个实例的 new
函数
let mut s = String::new();
let data = "initial contents";
let s = data.to_string();
// 该方法也可直接用于字符串字面值:
let s = "initial contents".to_string();
let s = String::from("initial contents"); // 等同于to_string
字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
更新字符串¶
使用push_str
和push
附加字符串
可以通过 push_str
方法来附加字符串 slice,从而使 String
变长,如示例所示。
let mut s = String::from("foo");
s.push_str("bar");
执行这两行代码之后,s
将会包含 foobar
。push_str
方法采用字符串 slice,因为我们并不需要获取参数的所有权。例如,示例中我们希望在将 s2
的内容附加到 s1
之后还能使用它。
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {s2}");
如果 push_str
方法获取了 s2
的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作!
push
方法被定义为获取一个单独的字符作为参数,并附加到 String
中。示例展示了使用 push
方法将字母 "l" 加入 String
的代码。
let mut s = String::from("lo");
s.push('l');
执行这些代码之后,s
将会包含 “lol”。
使用 + 运算符或 format! 宏拼接字符串
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
字符串 s3
将会包含 Hello, world!
。
s1
在相加后不再有效的原因,和使用 s2
的引用的原因,与使用 +
运算符时调用的函数签名有关。+
运算符使用了 add
函数,这个函数签名看起来像这样:
fn add(self, s: &str) -> String {
s1
的所有权,附加上从 s2
中拷贝的内容,并返回结果的所有权。
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}");
这些代码也会将 s
设置为 “tic-tac-toe”。format!
与 println!
的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 String
。这个版本就好理解的多,宏 format!
生成的代码使用引用所以不会获取任何参数的所有权。
索引字符串(并不支持)¶
一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。
let s1 = String::from("hello");
let h = s1[0];
3 | let h = s1[0];
| ^^^^^ `String` cannot be indexed by `{integer}`
Rust 的字符串不支持索引。因为rust的字符串是由utf8编码的
字符串 slice¶
let hello = "Здравствуйте";
let s = &hello[0..4];
当使用&hello[0..1]
时,会在运行时panic
因为有效的 Unicode 标量值可能会由不止一个字节组成。
中文3个,英文1个
所以尽量避免使用
遍历字符串¶
for c in "Зд".chars() {
println!("{c}");
}
output
З
д
for b in "Зд".bytes() {
println!("{b}");
}
output
208
151
208
180
`
Hash Map¶
最后介绍的常用集合类型是 哈希 map(hash map)。HashMap<K, V>
类型储存了一个键类型 K
对应一个值类型 V
的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。
哈希 map¶
use std::collections::HashMap; // 导入库
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
注意必须首先 use
标准库中集合部分的 HashMap
。
访问哈希 map中的值¶
可以通过 get
方法并提供对应的键来从哈希 map 中获取值,如示例所示:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);
get
方法返回 Option<&V>
,如果某个键在哈希 map 中没有对应的值,get
会返回 None
。程序中通过调用 copied
方法来获取一个 Option<i32>
而不是 Option<&i32>
,接着调用 unwrap_or
在 score
中没有该键所对应的项时将其设置为零。
遍历哈希 map¶
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{key}: {value}");
}
这会以任意顺序打印出每一个键值对:
Yellow: 50
Blue: 10
哈希 map 和所有权¶
对于像 i32
这样的实现了 Copy
trait 的类型,其值可以拷贝进哈希 map。对于像 String
这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者,如示例所示:
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 这里 field_name 和 field_value 不再有效,
当 insert
调用将 field_name
和 field_value
移动到哈希 map 中后,将不能使用这两个绑定。
如果将值的引用插入哈希 map,这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。
更新哈希 map¶
覆盖一个值
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
只在键没有对应值时插入键值对
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
entry
,它获取我们想要检查的键作为参数。entry
函数的返回值是一个枚举,Entry
,它代表了可能存在也可能不存在的值。Entry
的 or_insert
方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。
根据旧值更新一个值
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
split_whitespace
方法返回一个由空格分隔 text
值子 slice 的迭代器。or_insert
方法返回这个键的值的一个可变引用(&mut V
)。这里我们将这个可变引用储存在 count
变量中,所以为了赋值必须首先使用星号(*
)解引用 count
。这个可变引用在 for
循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。
哈希函数¶
HashMap
默认使用一种叫做 SipHash 的哈希函数,它可以抵御涉及哈希表(hash table)1 的拒绝服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的算法,不过为了更高的安全性值得付出一些性能的代价。
你可以指定一个不同的 hasher 来切换为其它函数。hasher 是一个实现了 BuildHasher
trait 的类型。