五、使用结构体来组织相关联的数据
struct ,结构体
- 自定义的数据类型
- 命名相关联的值,打包 -> 有意义的组合
(1)定义和实例化 struct
定义 struct
- 使用 struct 关键字,并为整个 struct 命名
- 花括号内,为所有 字段 定义名称和类型
例:
rust
struct User {
username:String,
email:String,
age:i32,
active:bool,
}
实例化 struct
创建 struct 实例
- 为每个字段指定具体值
- 无需按照声明顺序
- 无 mut 不可更改
rust
struct User {
username:String,
email:String,
age:i32,
active:bool,
}
fn main() {
let user1 = User { //无 mut ,不可更改
username:String::from("MOON"),
email:String::from("example@123.com"),
age:19,
active:true,
};
}
取出一个值:
rust
struct User {
username:String,
email:String,
age:i32,
active:bool,
}
fn main() {
let mut user1 = User {
username:String::from("MOON"),
email:String::from("example@123.com"),
age:19,
active:true,
};
println!("{}",user1.email);
}
==注:==一旦 struct 的实例是可变的,那么实例中所有的字段都是可变的
struct 作为函数返回值
例:
rust
struct User {
username:String,
email:String,
age:i32,
active:bool,
}
fn main() {
let user1 = demo(String::from("admin"),String::from("example@123.com"),19,true);
// 传入 &str ,报错,所以转换成 string 类型
println!("{}",user1.email);
}
fn demo(username:String,email:String,age:i32,active:bool) -> User {
User {
username,
email,
age,
active, //简写 -> active:active,
}
}
struct 更新语法
基于某个 struct 实例来创建一个新的实例时使用
rust
//如,仅更换几个参数
let user2 = User {
email: String::from("example@123.com"),
username: String::from("admin"),
active: user1.active,
age: user1.age,
};
//使用 struct 更新语法:
let user2 = User {
email: String::from("example@123.com"),
username: String::from("admin"),
..user1
};
Tuple struct
概念:定义类似 tuple 的 struct ,叫做 tuple struct
- Tuple struct 整体有名,内部元素没有名
- 适用于给 tuple 起名,并使它不同于其他 tuple
定义 tuple struct :struct [名字] (元素类型,元素类型,元素类型,...)
例如:
rust
struct Color(i32,i32,i32);
struct Point(i32,i32,i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);
注: black 和 origin 是不同的类型,是不同的 tuple struct 的实例。
Unit-Like Struct
概念:可以定义无字段的 struct ,叫做 Unit-Like struct
适用于需要在某个类型上实现某个 trait,但在里面没有想要存储的数据
struct 数据的所有权
rust
struct User {
username: String,
email: String,
age: i32,
active: bool,
}
这里字段使用了 String 而不是 &str:
- 该 struct 实例拥有其所有的数据
- 只要 struct 实例是有效的,那么里面的字段数据也是有效的
struct 里也可以存放引用,但这需要使用生命周 - 生命周期保证只要 struct 实例是有效的,那么里面的引用也是有效的
- 如果 struct 里面存储引用,而不使用生命周期,就会报错
(2)struct 示例
计算长方形面积
基本实现:
rust
fn main() {
let w = 30;
let l = 50;
println!("长方形的面积为:{}",area(w,l));
}
fn area(width: u32, length: u32) -> u32 {
width * length
}
元组:
rust
fn main() {
let rect = (30,50);
println!("长方形的面积为:{}",area(rect));
}
fn area(dim:(u32, u32)) -> u32 { //元素没有名字,可读性差
dim.0 * dim.1
}
结合 struct 增强代码可读性
rust
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rect = &Rectangle {
width: (30),
length: (50),
};
println!("长方形的面积为:{}",area(&rect));
// 传递 &rect 引用,不会获取到所有权,以便后续使用
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
验证所有权:
rust
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rect = &Rectangle {
width: (30),
length: (50),
};
println!("长方形的面积为:{}",area(&rect));
// 传递 &rect 引用,不会获取到所有权,以便后续使用
println!("{}",rect.weigth);
// 单个元素调用
println!("如想输出全部元素{:?}",rect);
// 这种输出需要添加 debug 在开头
// #[derive(Debug)]
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
添加debug:
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rect = &Rectangle {
width: (30),
length: (50),
};
println!("长方形的面积为:{}",area(&rect));
println!("如想输出全部元素{:?}",rect);
// 这种输出需要添加 debug 在开头
// #[derive(Debug)]
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
运行结果:
{:?} -> {:#?} 后再输出:
(3)struct 方法
方法和函数类似:fn 关键字、名称、参数、返回值
方法与函数不同之处:
- 方法是在 struct (或 enum 、trait 对象)的上下文中定义
- 第一个参数是 self ,表示方法被调用的 struct 实例
定义方法:
rust
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
}
主函数中调用:
rust
fn main() {
let rect = &Rectangle {
width: (30),
length: (50),
};
println!("长方形的面积为:{}",rect.area()); // rect.方法名()
println!("{:#?}",rect);
}
定义方法
在 impl 块里定义方法
方法的第一个参数可以是 &self ,也可以获得其所有权 或可变借用
方法调用的运算符
C/C++:object -> something() 和 (*object).something()
Rust 没有 -> 运算符
Rust 会自动引用或解引用
- 在调用方法时就会发生这种行为
在调用方法时,Rust 根据情况自动添加 & 、&mut 或 * ,以便 object 可以匹配方法的签名
例如:
rust
p1.distance(&p2);
(&p1).distance(&p2);
//两者表达效果相同
判断长方形能否包含另一个长方形
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
fn main() {
let rect1 = &Rectangle {
width: (30),
length: (50),
};
let rect2 = &Rectangle {
width: (10),
length: (40),
};
let rect3 = &Rectangle {
width: (35),
length: (55),
};
println!("{}",rect1.can_hold(&rect2));
//判断rect1 能否包含 rect2
println!("{}",rect1.can_hold(&rect3));
//判断rect1 能否包含 rect2
}
关联函数
概念:可以在 ipml 块里定义不把 self 作为第一参数的函数,叫做关联函数
- 例如:String::from()
关联函数通常用于构造器
例:正方形
rust
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
length: size,
}
}
输出正方形面积:
rust
let s = Rectangle::square(50);
println!("正方形的面积是:{}",s.area());
:: 符号:
- 关联函数
- 模块创建的命名空间
多个 impl 块
每个 struct 允许拥有多个 impl 块
六、枚举与模式匹配
(1)定义枚举
定义枚举
IP地址:IPv4、IPv6
rust
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {
}
将数据附加到枚举的变体中
优点:
- 不需要额外使用 struct
- 每个变体可以拥有不同的类型以及关联的数据量
例:
rust
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
}
更改为:
rust
enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddrKind::V4((127), (0), (0), (1));
let loopback = IpAddrKind::V6((::1));
}
为枚举定义方法 (impl)
rust
enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {}
}
fn main() {
let q = Message::Quit;
let m = Message::Move { x: (12), y: (24) };
let w = Message::Write(("Hello"));
let c = Message::ChangeColor((0), (255), (255));
m.call();
}
(2)Option枚举
- 定义于标准库中
- 在 Prelude (预导入模型 )
- 描述了:某个值可能存在(某种类型)或不存在的情况
Rust 没有 Null
其他语言:
- Null 是一个值,它表示"没有值"
- 一个变量可以处于两种状态:空值(null)、非空
Null 的问题在于:如果使用 Null 值像非 Null 值一样,会引发错误
概念:因某种原因而变为无效或缺失的值
Rust中类似 Null 概念的枚举-Option<T>
标准库中的定义:
rust
enum Option<T> {
Some(T),
None
}
包含于 预导入模块 中:
- Option<T>
- Some(T)
- None
例:
rust
fn main() {
let some_number = Some(5);
let some_string = Some("A String");
let absent_number: Option<i32> = None;
}
==注:==Option<T> != T
Option<T> 相较于 Null
- Option<T> 和 T 是不同的类型
- 若想使用 Option<T> 中的 T 必须先转换成 T
(3)控制流运算符 match
- 允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码
- 模式可以是字面值、变量名、通配符等
rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn Value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {}
绑定值的模式
匹配的分支可以绑定到被匹配对象的部分值
- 因此,可以从 enum 中提取值
rust
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn Value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}", state);
25
}
}
}
fn main() {
let c = Coin::Quarter(UsState::Alaska);
println!("{}", Value_in_cents(c));
}
匹配 Option<T>
rust
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
match 匹配必须穷举所有可能
假设没有列出 None 可能性
rust
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
//None => None
Some(i) => Some(i + 1),
}
}
运行结果
如,没有全部列出的必要的情况,可用 _ 代替其余值,必须在最后
rust
1 => println!(1),
2 => println!(2),
3 => println!(3),
_ => (),
(4)简单控制流 if let
- 处理只关心一种匹配而忽略其它匹配的情况
- 更少的代码、缩进、以及模板
- 放弃了穷举
例:对比
rust
fn main() {
let v = Some(0u8);
match v {
Some(3) => println!("three"),
_ => println!("others"),
}
if let Some(3) = v {
println!("three");
}
}
if let 搭配 else
rust
fn main() {
let v = Some(0u8);
match v {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = v {
println!("three");
} else {
println!("others");
}
}
七、使用包、单元包及模块管理项目
(1)Package、Crate、Module
Rust 的代码组织
代码组织主要包括:
- 哪些细节可以暴露,哪些细节是私有的
- 作用域内哪些名称有效
模块系统: - Package(包):Cargo 的特性,构建、测试、共享 crate
- Crate(单元包) :一个模块树,可以产生一个 library 或可执行文件
- Module(模块):控制代码的组织、作用域、私有路径
- Path(路径):为 struct、function或module等项命名的方式
Package 和 Crate
Crate 的类型:
- binary
-library
Crate Root: - 源代码文件
- Rust 编译器从这里开始,组成 Crate 的根 Module
Package: - 包含 1 个 Cargo.toml ,描述了如何构建这些 Crates
- 只能包含 0-1 个 library crate
- 可以包含任意数量的 binary crate
- 必须至少包含一个 crate(library 或 binary)
创建
cargo new project
Cargo.toml(配置文件)
rust
[package]
name = "project"
version = "0.1.0"
edition = "2024"
[dependencies]
main.rc(源代码文件、入口文件)
Cargo 的惯例
src/main.rs:
- binary crate 的 crate root
- crate 名与 package 名相同
src.lib.rs: - package 包含一个 library crate
- library crate 的 crate root
- crate 名与 package 名相同
一个 Package 可以同时包含 src/main.rs 和 src/lib.rs - 一个 binary crate ,一个 library crate
- 名称与 package 名相同
一个 Package 可以有多个 binary crate: - 文件放在 src/bin
- 每个文件是单独的 binary crate
crate 的作用
将相关功能组合到一个作用域内,便于项目间进行共享
- 防止冲突
如:rand crate,访问它的功能需要通过它的名字:rand
定义 module 来控制作用域和私有性
Module:
- 在一个 crate 内,将代码进行分组
- 增加可读性,易于复用
- 控制项目(item )的私有性
建立 module: - mod 关键字
- 可嵌套
- 可包含其他项(struct 、enum 、常量 、trait 、函数 )的定义
例:
rust
mod front_of_house {
mod hosting {
fn add_to_eaitlist() {}
fn seat_at_table() {}
}
mod srving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
Module
src/main.rs 和 src/lib.rs 叫做 crate roots:
- 这两个文件(任意一个)的内容形成了名为 crate 的模块,位于整个模块树的根部
tree
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── serve_order
├── take_order
└── take_payment
(2)路径
为了在 Rust 的模块中找到某个条目,需要使用路径
路径的两种形式:
- 绝对路径:从 crate root 开始,使用 crate 名 或 字面值 crate
- 相对路径:从当前模块开始,使用 self 、super 或当前模块标识符
路径至少由一个标识符组成,标识符之间使用 ::
例:
rust
mod fount_of_house {
mod hosting{
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
}
注:该例子无法正常运行,存在调用私有
私有边界
- 模块不仅可以组织代码,还可以定义私有边界
- 如果想把 函数 或 struct 等设为私有,可以将它放到某个模块中
- Rust 中所有的条目,(函数、方法、struct、enum、模块、常量)默认是私有的
- 父级模块无法访问子模块中的 私有条目
- 子模块里可以使用所有祖先模块中的条目
pub 关键字
将某些条目标记为公共的
rust
mod fount_of_house {
pub mod hosting{
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
//正常运行
front_of_house::hosting::add_to_waitlist();
}
(3)super、pub struct、enum
super 关键字
super:用于访问父级目录,类似文件系统中的 ...
rust
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
pub struct
声明 struct 为公共的
例:
rust
mod back_of_house {
pub struct Breakfast{
pub xxx: String, //公有
yyy: String, //私有
}
}
pub struct:
- struct 是公共的
- struct 的字段默认是私有的
struct 的字段需要单独设置 pub 来变为公有
pub enum
pub 放在 enum 前:
- enum 是公共的
- enum 的变体也是公共的
例:
rust
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
(4)use 关键字
可以使用 use 关键字将路径导入到作用域内
- 仍遵循私有性规则
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
使用 use 指定相对路径
use 的习惯用法
- 函数:将函数的父级模块引入作用域(指定到父级)
- struct、enum,其他:指定完整路径(指定到本身)
例:(哈希表)
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1,2);
}
- 同名条目,指定到父级
rust
use std::fmt;
use std::io;
fn f1() -> fmt::Result {}
fn f2() -> io::Result {}
fn main() {}
as 关键字
as 关键字可以为引入的路径指定本地的别名
rust
use std::fmt::Result;
use std::io::Result as IoResult;
fn f1() -> Result {}
fn f2() -> IoResult {}
fn main() {}
使用 pub use 重新导出名称
- 使用 use 将路径导入到作用域后,该名称在此作用域是 私有 的
例:
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
pub use:重导出
- 将条目引入作用域
- 该条目可以被外部代码引入到它们的作用域
使用外部包(package)
1、Cargo.toml 添加依赖的包
- https://crate.io/
2、use 将特定条目引入作用域
例:Cargo.toml引入 rand
rust
[package]
name = "project"
version = "0.1.0"
edition = "2024"
[dependencies]
rand = "0.5.5"
函数中引用
rust
use rand::Rng;
标准库(std)也被当做外部包
- 不需要修改 Cargo.toml 来包含 std
- 需要使用 use 将 std 中的特定条目引入当前作用域
使用嵌套路径清理大量的 use 语句
如果使用同一个包或模块下的多个条目
可使用嵌套路径在同一行内进行多条引入
- 路径相同部分::{差异部分}
例:
rust
情况一:
// use std::cmp::Ordering;
// use std::io;
use std::{cmp::Ordering, io};
情况二:
// use std::io;
// use std::io::Write;
use std::io{self, Write};
通配符*
使用 * 可以把路径中所有的公共条目都引入到作用域
rust
use std::collections::*;
(5)模块拆分
将模块内容移动至其他文件
模块定义时。如果模块名后边是";" ,而不是代码块:
- Rust 会从与模块同名的文件中加载内容
- 模块树的结构不会变化

在同目录下找,再次拆分则需要创建文件夹
随着模块逐渐变大,该技术可以将模块的内容移动到其他文件中
八、通用集合类型
(1)Vector
使用 Vector 存储多个值
Vec<T> ,叫做 vector
- 由标准库提供
- 可存储多个值
- 只能存储相同类型的数据
- 值在内存中连续存放
创建 Vector
Vec::new 函数
例:
rust
fn main() {
let v: Vec<i32> = Vec::new();
}
使用初始值创建 Vec<T> ,使用 vec! 宏
rust
fn main() {
let v = vec![1, 2, 3];
}
更新 Vector
向 Vector 添加元素,使用 push 方法
例:
rust
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
v.push(4);
}
删除 Vector
与其他 struct 一致,离开作用域后自动清理
例:
rust
fn main() {
let v = vec![1, 2, 3, 4];
}
读取 Vector 的元素
两种方式可以引用 Vector 里的元素
- 索引
- get 方法
例:
rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
//索引
let third: &i32 = &v[2];
println!("The third element is {}", third);
//get
match v.get(2) {
Some(third) => println!("The third element is {}",third),
None => println!("There is no third element"),
}
}
索引 vs get 处理访问越界
- 索引:panic
- get:返回 None
所有权和借用规则
不能在同一作用域内同时拥有可变和不可变引用
遍历 Vector 中的值
for 循环:
例:
rust
// 不可变
fn main() {
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
}
// 可变
fn main() {
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
// *i 解引用
}
for i in &v {
println!("{}", i);
}
}
使用 enum 来存储多种数据类型
- Enum 的变体可以附加不同类型的数据
- Enum 的变体定义在同一个 enum 类型下
例:
rust
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
fn main() {
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
}
(2)String
概念: Byte 的集合,通过一些方法将 byte 解析为文本
字符串是什么
Rust 的核心语法层面 ,只有一个字符串类型:字符串切片 str(或 &str)
字符串切片:对存储在其他地方、UTF-8 编码的字符串的引用
- 字符串字面值:存储在二进制文件中,也是字符串切片
String 类型: - 来自 标准库 而不是 核心语言
- 可增长、可修改、可拥有
- UTF-8
通常所说的字符串
String 和 &str
- 标准库里用的多
- UTF-8 编码
Rust提供的其他字符串类型
OsString、OsStr、CString、CStr
- String vs Str 后缀:拥有或借用的变体
- 可存储不同编码的文本或在内存中以不同的形式展现
Library crate (第三方库)针对存储字符串可提供更多的选项
创建字符串(String)
Vec<T> 的操作大部分可用于 String
1、String::new() 函数
例:
rust
fn main() {
let mut s = String::new();
}
2、使用初始值来创建 String:
- to_string() 方法,可用于实现了 Display trait 的类型,包括字符串字面值
例:
rust
fn main() {
let data = "initial contents";
let s = data.to_string();
let s1 = "initial contents".to_string();
}
- String::from() 函数,从字面值创建 String
例:
rust
fn main() {
let s = String::from("initial contents");
}
更新 String
- push_str() 方法:把一个字符串切片附加到 String
例:
rust
fn main() {
let mut s = String::from("foo");
s.push_str("bar");
println!("{}", s); //&str
}
- push() 方法:把单个字符附加到 String
例:
rust
fn main() {
let mut s = String::from("lo");
s.push('l');
}
- + 拼接字符串
例:
rust
fn main() {
let s1 = String::from("Hello,");
let s2 = String::from("World!");
let s3 = s1 + &s2;
println!("{}",s3);
//拼接后 s1 无法使用,s2 可以正常使用
}
- format! :连接多个字符串
例:
rust
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
//let s = s1 + "-" + &s2 + "-" + &s3;
let s = format!("{}-{}-{}",s1,s2,s3);
println!("{}", s);
}
String
类型不支持索引语法
内部表示
String 是对 Vec<T> 的包装
- len()方法(长度)
例:
rust
fn main() {
let len = String::from("Hola").len();
println!("{}", len);
}
Bytes,Scalar Values,Grapheme Clusters
- 字节
- 标量值
- 字符簇
例:
rust
let i = "xxxxxx";
i.bytes //字节
i.chars //标量值
Rust 不允许对 String 进行索引的最后一个原因:
- 索引操作应消耗一个常量时间(O(1))
- String 无法保证:需要遍历所有内容,确定有多少个合法字符
切割 String
使用 [] 和 一个范围 来创建字符串的切片
例:
rust
fn main() {
let hello = "abcdefghij";
let s = &hello[0..4];
}
- 谨慎使用
- 如切割时跨越了字符边界,程序就会 panic
- (b1,b2),(b3,b4),(b5,b6),(b7,b8)
遍历 String 方法
标量值:chars()方法
字节:bytes()方法
注:
Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为
- 程序员必须投入精力处理 UTF-8 数据
可防止在开发后期处理设计非 ASCII 字符的错误
(3)HashMap
HashMap<K,V>
键值对的形式存储数据,一个键(Key )对应一个值(Value )
Hash 函数:决定如何在内存中存放 K 和 V
使用场景:通过 K(任何类型)来寻找数据,而不是通过索引
创建 HashMap
创建空 HashMap: new() 函数
添加数据:insert() 方法
例:
rust
use std::collections::HashMap;
fn main() {
// let mut scores: HashMap<String, i32> = HashMap::new();
let mut scores = HashMap::new(); //必须声明类型
scores.insert(String::from("Blue"), 10);
}
注:
- HashMap 用的较少,不在 Prelude(预导入) 中,需手动引入
- 标准库支持少,没有内置 宏 来创建 HashMap
- 数据存储在 heap 上
- 同构中,一个 HashMap中:
所有 K 必须同类型
所有 V 必须同类型
另一种创建方式:collect 方法
在元素类型为 Tuple 的 Vector 上使用 collect 方法,可以组建一个 HashMap:
- 要求 Tuple 的两个值:一个作为 K ,一个作为 V
- collect 方法可以把数据整合成很多种集合类型,包括 HashMap
- 返回值需要显示指明类型
例:
- 返回值需要显示指明类型
rust
use std::collecctions::HashMap;
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let intial_scores = vec![10, 50];
let scores: HashMap<_, _> =
teams.iter().zip(intial_scores.iter()).collect();
// zip() 合成一个元组
}
HashMap 和所有权
对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中
对于拥有所有权的值(例如 String),值会被移动,所有权会转移给 HashMap
例:
rust
use std::collections::HashMap;
fn main() {
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
//map.insert(&field_name, &field_value);
// 没有直接获取所有权,后续可正常使用
println!("{}: {}", field_name, field_value); //所有权移交,此处报错
}
如果将值的引用插入到 HashMap,值本身不会移动
- 在 HashMap 有效期间,被引用的值必须保持有效
访问 HashMap 中的值
get 方法
- 参数:K
- 返回:Option<&V>
例:
rust
use std::collections::HashMap;
fn main() {
let main 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);
match score {
Some(s) => println!("{}", s),
None => println!("team not exist"),
}
}
遍历 HashMap
for 循环
例:
rust
use std::collections::HashMap;
fn main() {
let main scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (k, v) in %scores {
println!("{}: {}", k, v);
}
}
更新HashMap<K, V>
HashMap 大小可变
每个 K 同时只能对应一个 V
更新 HashMap 中的数据:
1、K 已经存在,对应一个 V
- 替换现有的 V
- 保留现有的 V ,忽略新的 V
- 合并现有的 V 和新的 V
2、K 不存在 - 添加一对 K ,V
替换现有的 V
向 HashMap 插入一对 K 、V ,再插入同样的 K ,不同的 V ,就会替换掉原来的 V
例:
rust
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 20);
println!("{:?}", scores);
}

只在 K 不对应任何值的情况下,插入 V
entry 方法:检查指定的 K 是否对应一个 V
- 参数为 K
- 返回 enum Entry:代表值是否存在
例:
rust
use std::collections::HashMap;
fn main() {
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 的 or_insert() 方法 :
- 如果 K 存在,返回到对应的 V 的一个可变引用
- 如果 K 不存在,将方法参数作为 K 的新值插入,返回该值的可变引用
基于现有 V 更新 V
例:
rust
use std::collections::HashMap;
fn main() {
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};
}

Hash 函数
默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗拒绝服务攻击
- 不是最快的Hash算法
- 具有更好的安全性
可以指定不同的 hasher 来切换到另一个函数 - hasher 是实现 BuildHasher trait 的类型