👈 7 泛型和特征

字符串 String

String 被实现为一个带有一些额外保证、限制和功能的 Vec<u8> 的封装。

创建:

// new()
let s = String::new();
 
// from()
let s = String::from("Rust");
 
// to_string()
let s = "Rust".to_string();

附加:

// push_str()
// 接收 &str, 不移动所有权
let mut s = String::from("R");
s.push_str("us"); // "Rus"
 
// push()
// 接收 char
s.push('t'); // "Rust"

拼接:

// + 运算符
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1 被移动, 不能继续使用
// + 重载了 String::add:
fn add(self, s: &str) -> String {
// 调用时, &s2 由 &String 强转为 &str
 
// format! 宏
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let ttt = format!("{s1}-{s2}-{s3}");

遍历:

// chars()
for c in "Rust".chars()
 
// bytes()
for b in "Rust".bytes()

动态数组 Vector

创建

动态数组类型为 Vec<T>

let v: Vec<i32> = Vec::new();

在这里显式地标注了 i32,因为从 Vec:new() 无法推断元素类型,不过可以在创建后向其中加入元素以省去类型标注:

let mut v = Vec::new();
v.push(1);

如果预先知道元素个数,可以调用 Vec::with_capacity(capacity),提升性能。

还可以用宏 vec! 创建,这种方式可以接受初始值,同时也无需标注类型:

let v = vec![1, 2, 3];

访问

访问元素时,可以用索引或者 get

let v = vec![1, 2, 3];
 
// 索引
let rval: &i32 = &v[1];
 
// get
match v.get(1) {
	Some(val) => println!("v[1] = {val}"),
	None => println!("v[1] does not exist"),
}
// 或者
let some_num = v.get(1).unwrap();
方式越界时解构
索引panic无需
get得到 None需要

在索引可能越界时,应使用 get 访问,否则应使用性能更好的索引访问。

借用

let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("v[0]={}", first);

这段代码无法过编:

let first = &v[0];
            - immutable borrow occurs here
 
v.push(4);
^^^^^^^^^ mutable borrow occurs here
 
println!("v[0]={}", first);
                    ----- immutable borrow later used here

解释如下:

let mut v = vec![1, 2, 3];
let first = &v[0]; // 不可变借用 // ok
v.push(4); // 可变借用 // ok
println!("v[0]={}", first); // 再次使用不可变借用 // not ok

这样设计是有道理的:数组的大小是可变的,当旧数组的大小不够用时,Rust 会重新分配一块更大的内存空间,然后把旧数组拷贝过来。这种情况下,之前的引用会指向一块无效的内存。

在长大之后,我们感激人生路上遇到过的严师益友,正是因为他们,我们才在正确的道路上不断前行,虽然在那个时候,并不能理解他们。而 Rust 就如那个良师益友,它不断的在纠正我们不好的编程习惯,直到某一天,你发现自己能写出一次性通过的漂亮代码时,就能明白它的良苦用心。

迭代遍历

Vector 的迭代遍历更加安全、高效:

let mut v = vec![1, 2, 3];
 
// 不可变迭代遍历
for x in &v {
	// ...
}
 
// 可变迭代遍历
for x in &mut v {
	*x += 10;
	// ...
}

存储不同类型的元素

枚举实现:

#[derive(Debug)]
 
enum IpAddr {
    V4(String),
    V6(String),
}
 
fn main() {
    let ips = vec![
        IpAddr::V4("127.0.0.1".to_string()),
        IpAddr::V6("::1".to_string()),
    ];
    for ip in ips {
        display_ip(ip)
    }
}
 
fn display_ip(ip: IpAddr) {
    println!("{:?}", ip);
}

V4V6 类型的元素存储在枚举 IpAddr 中,再将枚举的实例存储在动态数组 ips 中。

特征对象实现:

trait IpAddr {
    fn display(&self);
}
 
struct V4(String);
impl IpAddr for V4 {
    fn display(&self) {
        println!("ipv4: {:?}", self.0)
    }
}
 
struct V6(String);
impl IpAddr for V6 {
    fn display(&self) {
        println!("ipv6: {:?}", self.0)
    }
}
 
fn main() {
    let ips: Vec<Box<dyn IpAddr>> = vec![
        Box::new(V4("127.0.0.1".to_string())),
        Box::new(V6("::1".to_string())),
    ];
    for ip in ips {
        ip.display();
    }
}

为结构体 V4V6 实现特征 IpAddrips 的元素类型为 Box<dyn IpAddr>,意为用 Box 包裹的特征 IpAddr 对象。

特征对象动态数组更灵活,也更常见。

排序

Rust 的库中有两种排序:稳定的 sortsort_by,不稳定的 sort_unstablesort_unstable_by

稳定排序的缺点在于需要额外分配原数组一半大小的空间,且速度更慢。

Vec<i32> 排序:

let mut i32s = vec![1, 10, 4, 7];
i32s.sort_unstable();
assert_eq!(i32s, vec![1, 4, 7, 10]); // success

类似地,尝试对 Vec<f32> 排序:

// not ok
let mut f32s = vec![1.3, 8.9, 4.5, 7.2];
f32s.sort_unstable();
assert_eq!(f32s, vec![1.3, 4.5, 7.2, 8.9]); // fail

无法过编,因为 f32 有特殊值 NAN,无法与其他值比较,因此 f32 实现的是部分可比较的特性 PartialOrd,但排序要求实现全部可比较的特性 Ord

需要改为使用 partial_cmp

let mut f32s = vec![1.0, 5.6, 10.3, 2.0, 15f32];    
f32s.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());    
assert_eq!(f32s, vec![1.0, 2.0, 5.6, 10.3, 15f32]); // success

Vec<struct> 排序:

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}
 
impl Person {
    fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
}
 
fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("John".to_string(), 1),
    ];
    // 自定义按年龄倒序排序
    people.sort_unstable_by(|a, b| b.age.cmp(&a.age));
    println!("{:?}", people);
}

如果为 Person 实现 Ord 特性,就无需自定义排序了。实现 Ord 需要实现 OrdEqPartialOrdPartialEq 这些属性,但它们都可以 derive

#[derive(Debug, Ord, Eq, PartialOrd, PartialEq)]
struct Person {
    name: String,
    age: u32,
}
 
impl Person {
    fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
}
 
fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("Al".to_string(), 30),
        Person::new("John".to_string(), 1),
        Person::new("John".to_string(), 25),
    ];
    people.sort_unstable();
    println!("{:?}", people);
}

哈希表 HashMap

创建

使用 new() 创建

不同于 StringVec 等,HashMap 没有包含在 prelude 中,需要手动引入。

use std::collections::HashMap; // 引入
 
// 创建 HashMap
let mut gems = HashMap::new();
 
// 填入键值对
gems.insert("ruby", 1);
gems.insert("emerald", 2);
gems.insert("wastestone", 18);

使用迭代器和 collect() 创建

设想有一个 Vec<(String, u32)> 存储了球队名称与得分的元组,现需要将其写入一个 HashMap<String, u32>

可以这样手写:

let mut teams_map = HashMap::new();
for team in &teams_list {
	teams_map.insert(&team.0, team.1);
}

这很不 Rusty,可以改用 collect

let teams_map: HashMap<_, _> = teams_list.into_iter().collect();

into_iter() 将列表转换为迭代器,再用 collect() 收集,后者实际上支持生成多种目标类型的集合,因此需要显式地将 teams_map 标注为 HashMap<_,_>,表示需要一个 HashMap,否则会出错:

error[E0282]: type annotations needed
 
let teams_map = teams_list.into_iter().collect();
    ^^^^^^^^^
 
help: consider giving `teams_map` an explicit type

所有权转移

规则是相同的,将一个未实现 Copy 特征的变量传入 HashMap 后,所有权被转移。

let name = String::from("Sunface");
let age = 18;
let mut handsome_boys = HashMap::new();
handsome_boys.insert(name, age);
println!("{name}"); // not ok

如果在 HashMap 中使用引用类型,要确保该引用的生命周期不短于哈希表:

let name = String::from("Sunface");
let age = 18;
let mut handsome_boys = HashMap::new();
handsome_boys.insert(&name, age);
std::mem::drop(name); // not ok // 后文中再次借用
println!("{:?}", handsome_boys);

访问

get() 访问元素:

let mut gems = HashMap::new();
gems.insert("ruby", 1);
gems.insert("emerald", 2);
let rubies: Option<&i32> = gems.get("ruby");

&i32 借用 value,防止所有权转移。

也可以直接获得值类型:

let rubies: i32 = gems.get("ruby").copied().unwrap_or(0);

copied() 通过复制内容将 Option<&T> 转换为 Option<T>unwrap_or() 解构 Option<T>,如果得到 None,就返回指定的默认值 0

for-in 遍历 HashMap

for (gem, cnt) in &gems {
	// ...
}

更新

let mut gems = HashMap::new();
gems.insert("ruby", 1);
gems.insert("emerald", 2);
 
// 更新, 同时返回旧值
let rubies_old = gems.insert("ruby", 6);
// rubies_old == Some(1)
 
// 查询, 不存在时插入
let sapphires = gems.entry("sapphire").or_insert(0);
// *sapphires == 0
let emeralds = gems.entry("emerald").or_insert(1);
// *emeralds == 2

or_insert() 返回 &mut v 引用,可以通过该可变引用直接修改 HashMap 中对应的值,例如:

let cnt = gems.entry("ruby").or_insert(0);
*cnt += 1; // gems[ruby] == 1

要对 cnt 先解引用再更新,否则类型不匹配。等价 Java 代码:

map.merge(key, 1, Integer::sum);

哈希函数

一个类型能否作为 key 的关键在于能否进行相等比较(能否确认相同和区分不同),对应的特征为 std::cmp::Eq

f32f64 由于 NAN 的存在,没有实现 std::cmp::Eq,因此无法作为 key

哈希冲突越少,哈希函数越安全。

目前,HashMap 使用的哈希函数是 SipHash,它的性能一般,而安全性很好。实现算法时,可以考虑性能更好的哈希函数,例如 ahash

👉 9 生命周期