Struct
结构体,自定义的数据类型,为相关联的值命名,打包成有意义的组合。
定义及实例化
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool, // 最后的逗号不能省略
}
fn main() {
let user1 = User { // 字段顺序不必与定义相同,但是字段一个都不能少
email: String::from("[email protected]"),
username: String::from("abc"),
active: true,
sign_in_count: 556,
};
}
复制代码
// 如果想要修改 user1 里面的值那么实例化的时候需要加 mut 关键字,所有字段都是可变的,不能部分可变
fn main() {
let mut user1 = User { // 字段顺序不必与定义相同,但是字段一个都不能少
email: String::from("[email protected]"),
username: String::from("abc"),
active: true,
sign_in_count: 556,
};
user.email = String::from("[email protected]");
}
复制代码
fn build_user(email: String, username: String) -> User {
User {
email, // 可以简写,类似 JS
username,
active: true,
sign_in_count: 0,
}
}
复制代码
fn main() {
let user1 = User {
email: String::from("[email protected]"),
username: String::from("abc"),
active: true,
sign_in_count: 556,
};
let user2 = User {
email: String::from("[email protected]"),
username: String::from("aaa"),
..user1 // 使用 .. 把 user1 中相同字段赋值到 user2,与 JS 的扩展运算符类似
};
}
复制代码
Tuple struct
定义类似 tuple 的 struct,叫做 tuple struct。Tuple struct 整体有个名,但里面的元素没有名,适用于想给整个 tuple 起名,并让它不同于其它 tuple,而且又不需要给每个元素起名。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// black 和 origin 是不同类型,也不能相等
复制代码
Unit-Like Struct(没有任何字段)
可以定义没有任何字段的 struct,因为与 “()”,单元类型类似得名。适用于需要在某个类型上实现某个 trait,但是在里面又没有想要存储的数据。
实例
#[derive(Debug)] // 让自定义类型可以派生于 Debug
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
length: 50,
};
println!("{}", area(&rect)); // 1500
println!("{:?}", rect); // Rectangle { width: 30, length: 50 }
println!("{:#?}", rect);
/*
Rectangle {
width: 30,
length: 50,
}
*/
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
复制代码
struct 的方法
struct Rectangle {
width: u32,
length: u32,
}
// impl 可以有多个
impl Rectangle {
// 方法,第一个参数必须是 &self 或者 self,直接用”.“调用
fn area(&self) -> u32 {
self.width * self.length
}
// 关联函数,参数不能是 self,用"::"调用
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
length: size,
}
}
}
fn main() {
let rect = Rectangle {
width: 30,
length: 50,
};
let s = Rectangle::square(12);
println!("{}", rect.area());
println!("{}", s.area());
}
复制代码
枚举
定义和使用
enum IpAddrKind {
v4,
v6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let four = IpAddrKind::v4;
let six = IpAddrKind::v6;
let home = IpAddr {
kind: IpAddrKind::v4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::v6,
address: String::from("::1"),
};
}
复制代码
将数据附加到枚举的变体中:
enum IpAddr {
V4(String),
V6(String),
}
复制代码
优点:不需要额外使用 struct,每个变体可以拥有不同类型以及关联的数据量。
enum IpAddr {
V4(u8, u8, u8, u8),
v6(String),
}
fn main() {
let home = IpAddrKind::V4(127.0.0.1);
}
复制代码
枚举的变体中可以使用任意类型的数据,枚举还可以使用 impl
来定义方法:
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: 20, y: 30 };
let q = Message::Write(String::from("Hello"));
let q = Message::ChangeColor(0, 255, 255);
m.call();
}
复制代码
Option 枚举
定义于标准库中,在 Prelude (预导入模块)中,描述了某个值可能存在(某种类型)或不存在的情况。
Rust 中没有 null
,但是有类似概念的枚举,用 Option<T>
表示。
// 它包含在 Prelude 中,可以直接使用
enum Option<T> {
Some(T),
None,
}
复制代码
fn main() {
let some_number = Some(5);
let some_string = Some("A String");
let absent_number: Option<i32> = None;
// Option<i32> 不能与 i32 直接进行运算,必须先转换才可以,可以能避免空值泛滥的情况
}
复制代码
match
允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码。模式可以是字面值、变量名、通配符...
#[derive(Debug)]
enum USState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(USState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => { // 多行需要花括号
println!("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>
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 匹配必须穷举所有可能
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
// None => None, // 删除此行会报错,因为未覆盖所有情况
Some(i) => Some(i + 1),
}
}
复制代码
使用通配符 _
来替代剩下的所有可能,必须放在最后。
fn main() {
let v = 0u8; // 256 种可能
match v {
1 => println!("one"),
2 => println!("two"),
_ => (), // 指代剩下的所有可能
}
}
复制代码
if let
处理只关心一种匹配而忽略其它匹配的情况。
// match 写法
fn main() {
let v = 0u8; // 256 种可能
match v {
1 => println!("one"),
_ => (),
}
}
复制代码
// if let 写法
fn main() {
let v = 0u8; // 256 种可能
if let Some(1) = v {
println!("one");
}
}
复制代码
两种写法等价。
另外,也可以使用 else
语法。
fn main() {
let v = 0u8; // 256 种可能
if let Some(1) = v {
println!("one");
} else {
println!("others");
}
}
复制代码
模块系统
Package(包)
Cargo 的特性,让你构建、测试、共享 crate。
一个 Package 包含 1 个 Cargo.toml,它描述了如何构建这些 Crates,只能包含 0 - 1 个 library crate,可以包含任意数量的 binary crate(文件放在 src/bin 下,每个文件是单独的 binary crate),但必须至少包含一个 crate(library 或 binary 都行)。
Crate(单元包)
一个模块树,它可以产生一个 library 或可执行文件。
类型分为:binary 和 library。
Crate Root 是源代码文件,Rust 编译器从这里开始,组成你的 Crate 的根 Module。
两个惯例:
- src/main.rs 其实是一个 binary crate 的 crate root,当前 crate 的名称与 package 相同。
- src/lib.rs 其实是一个 library crate 的 crate root,package 包含一个library crate,当前 crate 的名称与 package 相同。
Cargo 把 crate root 文件交个 rustc 来构建 library 或 binary。
crate 的作用是将相关功能组合到一个作用域内,便于在项目间进行共享,防止冲突。
Module(模块)
让你控制代码的组织、作用域、私有路径。
在一个 crate 内,将代码进行分组,增加可读性,易于复用,控制项目(item)的私有性。
建立 module 使用 mod 关键字创建,支持嵌套。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
复制代码
Path(路径)
为 struct、function 或 module 等项命名的方式。
绝对路径:从 crate root 开始,使用 crate名或字面值 crate
相对路径:从当前模块开始,使用 self、super 或当前模块的标识符
路径至少有一个标识符组成,标识符之间使用 ::
。
// src/lib.rs
mod front_of_house {
pub mod hosting { // 默认是私有的
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() { // front_of_house 与 eat_at_restaurant 同级可以互相调用
crate::front_of_house::hosting::add_to_waitlist(); // 绝对路径
front_of_house::hosting::add_to_waitlist(); // 相对路径
}
复制代码
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order(); // super 代表进入上一级路径
}
}
复制代码
公共枚举里面的变体都是公共的,而公共结构体下面的字段默认都是私有的。
use
使用 use
来引入 module。
use std::collections::HashMap;
use std::collections::HashMap as hp; // 使用 as 可以起别名
复制代码
pub use 重新导出
// src/lib.rs
mod front_of_house {
pub mod hosting { // 默认是私有的
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting; // 这个模块不会暴露给外部
pub use crate::front_of_house::hosting; // 这个模块会暴露给外部
复制代码
使用外部包(package):
- cargo.toml 添加依赖包(package),默认是从 crates.io 去下载
- use 将特定条目引入作用域
- 标准库(std)也被当做外部包,但是不需要修改 cargo.toml 来包含 std,需要使用 use 将 std 中的特定条目引入当前作用域
使用嵌套路径来清理大量的 use 语句:
use std::cmp::Ordering;
use std::io;
// 可以简写为
use std::{cmp::Ordering, io};
复制代码
use std::io;
use std::io::Write;
// 可以简写为
use std::io::{self, Write};
复制代码
// * 为通配符,可以把路径中所有的公共条目引入到作用域
// 谨慎使用
// 一般测试的时候将所有被测试代码引入到 tests 模块会用
// 有时候被用于与导入(prelude)模块
use std::collections::*;
复制代码
常用集合
这些集合都是存储在堆内存上的,大小可变的数据。
Vector
由标准库提供的,可以存储多个值,且值的数据类型都相同,值在内存中连续存放。
fn main() {
let v: Vec<i32> = Vec::new(); // 创建
}
复制代码
fn main() {
let v = vec![1, 2, 3]; // 使用宏创建
}
复制代码
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2); // 添加值
}
复制代码
// 读取 vec 里面的元素
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2]; // 如果 index 范围越界,程序会 panic
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element"),
}
}
复制代码
// vec 的遍历
fn main() {
let v = vec![1, 2, 3, 4, 5];
for i in &v {
println!("{}", i);
}
}
复制代码
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
for i in &mut v {
*i += 50; // 解引用
}
for i in v {
println!("{}", i);
}
}
复制代码
// 结合 enum 来实现 vector 存储多种类型的数据
enum E {
Int(i32),
Float(f64),
Text(String),
}
fn main() {
let v = vec![
E::Int(3),
E::Text(String::from("aaa")),
E::Float(12.43),
];
}
复制代码
String
字符串是基于 Byte 的一些集合,并提供了一些方法,能够将 byte 解析为文本。Rust 的核心语音层面只有一个字符串类型:字符串切片(&str)。字符串切片是对存储在其他地方、UTF-8 编码的字符串的引用,字符串字面量存储在二进制中,也是字符串切片。
String 是来自标准库,而不是来自核心语言,可增长、可修改、可拥有,也是 UTF-8 编码。
创建
let mut s = String::new();
复制代码
let data = "abcd";
let s = data.to_string();
复制代码
let s = String::from("abcd");
复制代码
更新
fn main() {
let mut s = String::from("foo");
s.push_str("bar");
println!("{}", s);
}
复制代码
fn main() {
let mut s = String::from("foo");
s.push('a'); // 单个字符加到 String
println!("{}", s);
}
复制代码
fn main() {
let s1 = String::from("foo");
let s2 = String::from("bar");
let s3 = s1 + &s2; // 注意后面是字符串切片,前面 s1 无法再继续使用了
println!("{}", s3);
// println!("{}", s1); // 会报错
// println!("{}", s2);
}
复制代码
fn main() {
let s1 = String::from("foo");
let s2 = String::from("bar");
let s3 = String::from("abc");
let s = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s); // foo-bar-abc
}
复制代码
方法
fn main() {
let len = String::from("Hola").len();
println!("{}", len); // 4
}
复制代码
fn main() {
let len = String::from("哈哈").len();
println!("{}", len); // 6
}
复制代码
fn main() {
let w = String::from("哈哈");
for b in w.bytes() {
println!("{}", b); // 229 147 136 229 147 136
}
}
复制代码
fn main() {
let w = String::from("哈哈");
for c in w.chars() { // unicode 标量值
println!("{}", c); // 哈 哈
}
}
复制代码
切片
fn main() {
let w1 = String::from("abcde");
let w2 = String::from("你好啊");
let s1 = &w1[0..2];
let s2 = &w2[0..3];
// let s3 = &w2[0..2]; // 运行会报错
println!("{}", s1); // ab
println!("{}", s2); // 你
}
复制代码
HashMap
HashMap<K, V>,键值对的形式存储数据,一个键对应一个值。Hash 函数决定如何在内存中存放 K 和 V。
适用场景:通过 K(任何类型)来寻找数据,而不是通过索引。
数据存储在 heap 上。
HashMap 用的较少,所以不在 Prelude 中,标准库对其支持较少,没有内置的宏来创建 HashMap。
HashMap 是同构的,一个 HashMap 中,所有的 K 必须是同一种类型,所有的 V 必须是同一种类型。
创建
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new();
}
复制代码
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
}
复制代码
use std::collections::HashMap;
fn main() {
let teams = vec![String::from("blue"), String::from("Yellow")];
let intial_scores = vec![10, 50];
// _, _ 代表根据后面的数据类型进行类型推断
// zip 有拉链的意思,就是把两个 vec 像拉链一样拉起来,组合到一起
let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect();
}
复制代码
HashMap 和所有权
对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中,对于拥有所有权的值(如 String),值会被移动,所有权会转移给 HashMap。
use std::collections::HashMap;
fn main() {
let s1 = String::from("abc");
let s2 = String::from("bcd");
let mut map = HashMap::new();
map.insert(s1, s2);
println!("{}, {}", s1, s2); // 报错
}
复制代码
use std::collections::HashMap;
fn main() {
let s1 = String::from("abc");
let s2 = String::from("bcd");
let mut map = HashMap::new();
map.insert(&s1, &s2); // 传值的引用
println!("{}, {}", s1, s2); // 不报错
}
复制代码
获取值
use std::collections::HashMap;
fn main() {
let s1 = String::from("abc");
let s2 = String::from("bcd");
let mut map = HashMap::new();
map.insert(&s1, 10);
map.insert(&s2, 50);
let m = String::from("abc");
let s = map.get(&m);
match s {
Some(s) => println!("{}", s),
None => println!("None"),
}
}
复制代码
遍历
use std::collections::HashMap;
fn main() {
let s1 = String::from("abc");
let s2 = String::from("bcd");
let mut map = HashMap::new();
map.insert(&s1, 10);
map.insert(&s2, 50);
for (k, v) in &map {
println!("{}, {}", k, v);
}
}
复制代码
更新
K 已经存在了,对应一个 V:
- 替换现有 V
- 保留现有 V,忽略新的 V
- 合并现有的 V 和新的 V
K 不存在直接添加新的。
// 情况1
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("abc"), 10);
map.insert(String::from("abc"), 50);
println!("{:?}", map); // {"abc": 50}
}
复制代码
// 情况2
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("abc"), 10);
let e = map.entry(String::from("edf")); // entry 检查 K 是否存在
e.or_insert(50);
map.entry(String::from("abc")).or_insert(30);
println!("{:?}", map); // {"abc": 10, "edf": 50}
}
复制代码
// 情况3
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; // 解引用,并加1
}
println!("{:?}", map); // {"hello": 1, "world": 2, "wonderful": 1}
}
复制代码
Hash 函数
默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗拒绝服务(DoS)攻击,不过他不是最快的 Hash 算法,但是安全性比较强。
我们可以指定不同的 hasher 来切换到另一个函数。hasher 是实现 BuildHasher trait 的类型。