Rust 语法学习
一般 Struct
定义
- 和 C 语言一样,是一种自定义的数据类型
- 和 C 语言一样,为相关联的值命名,打包成有意义的组合
建立
- 使用 struct 关键字,并为整个 struct 命名。
- 在花括号内,为所有字段(Filed)定义名称和类型
1 2 3 4 5 6
| struct User { username:String, email:String, sign_in_count:u64, active:bool, }
|
实例化
- 想要使用 struct, 需要创建 struct 的实例;
- 为每个字段指定具体值
- 无需按声明的顺序指定
1 2 3 4 5 6
| let user1 = User { email: String::from("acb@123.com"), username: String::from("Nikky"), active: true, sign_in_count: 556, };
|
访问与赋值
1 2 3 4 5 6 7 8 9
| let mut user1 = User { email: String::from("acb@123.com"), username: String::from("Nikky"), active: true, sign_in_count: 556, };
username1 = use1.username; user1.email = String::from("anotheremail@example.com");
|
一旦 struct 的实例是可变的,那么实例中所有的字段都是可变的。
作为函数返回值
1 2 3 4 5 6 7 8
| fn build_user(email:String,username:String) -> User { User{ email:email, username:username, active:true, sign_in_count:1, } }
|
字段初始化简写
- 当字段名与字段值对应变量名相同时,就可以使用字段初始化简写的方式:
改造上一个例子:
1 2 3 4 5 6 7 8
| fn build_user(email:String,username:String) -> User { User{ email, username, active:true, sign_in_count:1, } }
|
struct 更新语法
- 当你想基于某个 struct 实例来创建一个新实例的时候,可以使用 struct 更新语法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let user2 = User { email:String::from("another@123.com"), username:String::from("123"), active:user1.active, sign_in_count:user1.sign_in_count, };
就可以简写为:
let user2 = User { email:String::from("another@123.com"), username:String::from("123"), ..user1 };
|
Tuple struct
定义
- 可定义类似 tuple 的 struct,叫做 tuple struct.
- Tuple struct 整体有名字,其中的元素没有名字
- 适用于:想给整个 Tuple 起名,并让它不同于其他 Tuple, 而且又不需要给每个元素起名。
建立
- 使用 struct 关键字,后面是名字,以及里面元素的类型。
访问与修改
1 2 3 4 5 6 7 8
| struct Color(i32,i32,i32); struct Point(i32,i32,i32);
let mut black = Color(0,0,0); let origin = Point(0,0,0);
let black1 = black.1; black.1 = 1;
|
Unit-Like Struct
- 可以定义没有任何字段的 struct,叫做 Unit-Like struct.
- 适用于需要在某个类型上实现某个 trait,但是在里面又没有想要存储的数据。
struct 所有权
对于这样的例子:
1 2 3 4 5 6
| struct User { username:String, email:String, sign_in_count:u64, active:bool, }
|
- 这里的字段使用了 String 而不是 &str,表明:
- 该 struct 实例拥有其所有的数据。
- 只要 struct 实例是有效的,那么里面的字段数据也是有效的。
- struct 里面可以存放引用,但需要使用生命周期(后续内容)
这样做的目的是保证只要 struct 实例有效,里面的所有数据都是有效的。
struct 方法
- Rust 没有
->
引用符号
- Rust 会自动引用或解引用,所以无脑
.
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #[derive(Debug)] struct Rectangle { width: u32, length: u32, };
iml Rectangle { fn area(&self) -> u32 { self.width * self.length } }
fn main() { let rect = Rectangle { width: 30, length: 50, };
println!("{}",rect.area()); println!("{:#?}",rect); }
|
struct 关联函数
- 可以在 impl 块里定义不把 self 作为第一个参数的函数,它们叫关联函数(不是方法)
- 例如
String::from()
1 2 3 4 5 6 7 8 9 10 11 12 13
| iml Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, length: size } } }
fn main() { let s = Rectangle::square(20); }
|
枚举
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| enum IpAddrKind { v4, v6, }
fn main() { let four = IpAddrKind::v4; let six = IpAddrKind::v6;
route(four); route(six); route(IpAddrKind::v6); }
fn route(ip_addr:IpAddrKind) { ... }
|
将数据附加到枚举的变体中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| enum IpAddrKind { v4(u8, u8, u8, u8), v6(String), }
enum Message { Quit, Move { x:i32, y:i32, }, Write(String), ChangeColor(i32, i32, i32), }
fn main() { let home = IpAddrKind::V4(127, 0, 0, 1); let loopback = IpAddrKind::V6(String::from("123")); }
|
枚举方法
和 struct 一样。
Option 枚举
1 2 3 4
| enum Option<T> { Some(T), None, }
|
- 它包含在 Prelude (预导入模块) 中。可直接使用:
1 2 3 4 5 6
| fn main() { let some_number = Some(5); let some_string = Some("A String");
let absent_number: Option<i32> = None; }
|
Option 主要是表示别的语言中的 Null, 这种表示避免了很多了 Null 值的错误使用带来的 panic.
match
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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() {}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #[derive(Debug)] enum UsState { Alabama, Alaska, }
enum Coin { Penny, Nickel, Dime, Quarter, }
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)); }
|
- Rust match 匹配必须穷举所有值。
- 可以用
_
通配符来替代其余没列出的值。
if let
- 简单的控制流语句
- 处理只关心一种匹配而忽略其他匹配的情况。
- 更少的代码,更少的缩进,更少的模板代码。
- 放弃了穷举的可能。
- 可搭配 else.
1 2 3 4 5
| if let Some(3) = v { println("Three!") } else { println!("others!") }
|
Rust 代码组织
代码组织主要包括
- 哪些细节可以暴露,哪些细节是私有的。
- 作用域内哪些名称有效
模块系统:
- Package(包):Cargo 的特性,让你构建、测试、共享 crate.
- Crate(单元包):一个模块树,它可产生一个 Library 或可执行文件.
- Module(模块):use: 让你控制代码的组织、作用域、私有路径.
- Path(路径):为 struct、function 或 module 等项命名的方式.
Crate的类型
Crate Roof
- 是源代码文件
- Rust 编译器从这里开始,组成你的 Crate 的根 Module
一个 Package
- 包含 1 个 Cargo.toml, 它描述了如何构建这些 Crates.
- 只能包含 0 - 1 个 Library crate.
- 可以包含任意数量的 binary crate.
- 但必须至少包含一个 crate (library 或 binary).
Cargo 的惯例
- src/main.rs:
- binary crate 的 crate root
- crate 名与 package 名相同
- src/lib.rs:
- package 包含一个 library crate
- library crate 的 crate root
- crate 名与 package 名相同
- Cargo 把 crate root 文件交给 rustc 来构建 library 或 binary.
- 一个 Package 可以同时包含 src/main.rs 和 src/lib.rs
- 一个 binary crate, 一个 library crate.
- 名称与 package 名相同
- 一个 Package 可以有多个 binary crate.
- 文件放在 src/bin 下。
- 一个文件就是一个 binary crate.
Crate 的作用
- 将相关功能组合到一个作用域内,便于在项目间进行共享,这样做防止冲突
- 例如 rand crate, 访问它的功能需要通过它的名字:rand.
定义 module 来控制作用域和私有性
- 在一个 crate 内,将代码进行分组
- 增加可读性,易于复用
- 控制项目(item)私有性
- 使用 mod 关键字建立 module.
- module 可以嵌套.
1 2 3 4 5 6 7 8 9 10 11 12
| 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() {} } }
|
路径
- 为了在 Rust 的模块中找到某个条目,需要使用路径
- 路径的两种形式:
- 绝对路径:从 crate root 开始,使用 crate 名或字面值 crate.
- 相对路径:从当前模块开始,使用 self,super 或当前模块的标识符。
- 路径至少由一个标识符组成,标识符之间使用
::
1 2 3 4 5 6 7 8 9 10 11
| mod front_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(); }
|
但这样是无法通过编译的,这是由于 Rust 中条目默认为 private
私有边界
- 同级模块可以互相调用私有条目
- 子模块可以调用父模块私有条目
- 父模块不能调用子模块私有条目
pub 关键字
super 关键字
1 2 3 4 5 6 7 8 9 10 11
| fn serve_order() {}
mod back_of_house { fn fix_incorrect_order() { cook_order(); super::serve_order(); crate::serve_order(); }
fn cook_order() {} }
|
pub struct
- pub 放在 struct 前
- struct 变成公共
- 但 struct 中的字段仍然是私有的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| mod back_of_house{ pub struct Breakfast { pub toast: String, seasonal_fruit: String, }
impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), } } } }
pub fn eat_at_restaurant() { let mut meal = back_of_house::Breakfast::summer("Rye"); meal.toast = String::from("Wheat"); meal.seasonal_fruit = String::from("apple"); }
|
pub enum
use
可以使用 use 关键字将路径导入到作用域内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {
} } }
use crate::front_of_house::hosting; use front_of_house::hosting;
pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
|
as 关键字
as 关键字可以为引入路径指定本地的别名
1 2 3 4 5 6 7 8
| use std::fmt::Result; use std::io::Result as IoResult;
fn f1 -> Result {}
fn f2 -> IoResult {}
fn main() {}
|
pub use
- 使用 use 将路径导入到作用域内后,该名称在此作用域内是私有的
- pub use: 重导出
- 该条目引入作用域。
- 该条目可以被外部代码引入到它们的作用域。
使用外部包(package)
- Cargo.toml 添加依赖的包(package)
- use 将特定条目引入作用域
嵌套路径
- 使用嵌套路径清理大量的 use 语句
- 如果使用同一个包或模块下多个条目
- 可使用嵌套路径在同一行内将上述条目进行引入
- 形式:
路径相同的部分::{路径差异的部分}
1 2 3 4 5 6 7 8 9
| use std::{cmp::Ordering, io} 相当于 use std::io; use std::cmp::Ordering;
use std::io::{self, write}; 相当于 use std::io; use std::io::write;
|
通配符
- 使用
*
可以把路径中所有的公共条目都引入到作用域。
use std::collections;
use std::collections::*;
有啥区别?
以创建 HashMap 为例:
let a = collections::HashMap 第一种
let a = HashMap 第二种
将模块内容移动到其他文件
- 模块定义时,如果模块名后面是
;
而不是代码块
- Rust 会从与模块同名的文件中加载内容
- 模块树的结构不会发生变化
Rust 常用的集合
Vector
定义
Vec,叫做 vector.
- 由标准库提供
- 可存储多个值
- 只能存储相同类型的数据
- 值在内存中连续存放
创建
- Vec::new 函数:
let v : Vec<i32> = Vec::new();
如果下文可以推断出 Vector 类型,则无需在定义变量时给出类型。
- 使用初始值创建
Vec<T>
, 使用 vec!
宏:let v = vec![1, 2, 3];
更新
- 向 Vector 添加元素,使用 push 方法:
v.push(1);
- 读取 Vector 元素:
索引访问越界时会 panic
get访问越界时会返回 None
1 2 3 4
| let v = vec![100, 32, 57]; for i in &v { println("{}", i); }
|
使用 enum 来存储多种数据类型
笔者看来这算是一种 trick, 用来打破 Vector 只能存一种类型的问题。
String
注意事项
UTF-8 编码
创建
- String::new() 函数
- 使用初始值来创建 String.
- to_string() 方法, 可用于实现了 Display trait 的类型,包括字符串字面值。
- String::from() 函数,从字面值创建 String.
更新
- push_str() 方法: 把一个字符串切片附加到 String.
- push() 方法 : 把单个字符附加到 String.
- 拼接字符串:
+
- 格式化字符串:
format!
:format!("{}-{}-{}", s1, s2, s3);
这种格式化和 println! 的格式化输出一模一样,区别在于 format 返回字符串。
format 不会像 + 那样取得所有权。
1 2 3 4 5 6 7 8 9 10 11 12
| fn main() { let s1 = String::from("Hello,"); let s2 = String::from("World!"); let s3 = s1 + &s2;
println!("{}", s3); println!("{}", s2); println!("{}", s1);
}
|
字节、标量值、字形簇.这是 Rust 看待三种字符串的方式。
切割
- 使用
[]
和 一个范围
来创建字符串的切片(例子)
- 必须沿着字符边界切割,否则会 panic.
HashMap
HashMap<K,V>
- 键值对的形式存储数据,一个键值(key)对应一个值(value)
创建
- 创建空 HashMap: new 函数
- 使用 insert 方法添加数据。
- 在元素类型为 Tuple 的 Vector 上使用 Collect 方法,可以组建一个 HashMap:
- 要求 Tuple 有两个值:一个作为 K,一个作为 V
- Collect 方法可以把数据整合成很多种集合类型,包括 HashMap
- 返回值需要显式指明类型(理由是 Collect 可以返回很多数据结构)
1 2 3 4 5 6 7 8 9
| use std::collections::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(); }
|
HashMap 和所有权
- 对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中。
- 对于拥有所有权的值(例如 String),值会被移动,所有权会转移给 HashMap。
1 2 3 4 5 6 7 8 9 10 11
| 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);
println!("{}:{}", field_name, field_value); }
|
访问 HashMap 中的值
所以要通过 Match 来匹配值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| use std::collections::HashMap;
fn main() { 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);
match score { Some(s) -> println!("{}", s), None -> println!("team not exist"), }; }
|
遍历
for 循环
1 2 3 4 5 6 7
| use std::collections::HashMap;
fn main() { for (k, v) in &scores { println!("{}: {}", k, v); } }
|
更新
- 由于 HashMap 要求 K 唯一,所以在 HashMap 已经存在一对
<K,V>
的情景下,直接再插入<K,M>
会替换原有的<K,V>
- 一般情况下,我们需要保证只有 K 不对呀任何值的情况下,才插入 V。
- entry 方法: 检查指定的 K 是否对应一个 V
- 参数为 K
- 返回 enum Entry: 代表值是否存在。
1 2 3 4 5 6 7 8 9 10 11
| use std::collections::HashMap;
fn main() { let mut scores = HashMap::new();
scores.insert(String::from("Blue"),10);
let e = scores.entry(String::from("Yellow")); e.or_insert(50); scores.entry(String::from("Blue")).or_insert(50); }
|
or_insert 就是枚举中的方法。
如果 K 存在,返回到对应 V 的一个可变引用。
如果 K 不存在,将方法参数作为 K 的新值插进去,返回到这个值的可变引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use std::collections::HashMap;
fn main() { let text = "hello world wonderful world";
ley mut map = HashMap::new();
for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; }
println("{:#?}", map); }
|
Hash 函数
- 通过指定不同的 hasher 来切换到另一个哈希函数。
- 默认函数为安全性较高的哈希函数,可抵抗 Dos 攻击。
Rust 错误处理
解释说明
Rust 的可靠性:错误处理
错误的分类:
- 可恢复:例如文件未找到,可再次尝试
- 不可恢复:bug,例如访问的索引超出范围
Rust 没有类似异常的机制
- 可恢复错误:
Result<T,E>
- 不可恢复:
panic!
宏
不可恢复错误
- 当 panic! 宏执行:
- 程序会打印一个错误信息
- 展开(unwind)、清理调用栈(Stack)
- 退出程序
何为展开调用栈?
是 Rust 沿着调用栈往回走,清理每个遇到的函数中的数据。
何为终止调用栈?
是 Rust 不进行清理,立刻停止程序。
内存需要 OS 进行清理。
- 想让二进制文件更小,把设置从”展开”改为”终止”,在 Cargo.toml 中 profile 部分设置
可恢复错误
Result 枚举
1 2 3 4
| enum Result<T,E> { Ok(T), Err(E), }
|
1 2 3 4 5 6 7 8 9 10
| fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) -> file, Err(error) -> { painc!("Error opening file {:?}", error); } }; }
|
- 处理 Result 的方式也可以依靠
unwarp
表达式
- 如果 Result 结果是 Ok,返回 Ok 里面的值
- 如果 Result 结果是 Err,调用 panic!宏
上述例子简写为
1 2 3
| fn main() { let f = File::open("hello.txt").unwarp(); }
|
传播错误
1、传统形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::fs::File; use std::io; use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt");
let mut f = match f { Ok(file) -> file, Err(e) -> return Err(e), };
let mut s = String::new();
match f.read_to_string(&mut s) { Ok(_) -> Ok(s), Err(e) -> Err(e), } }
|
2. 以上也可以用 `?` 语法糖完成(`?`放在Result后面),作用是:
- 如果 Result 是 Ok: Ok 中的值就是表达式的结果,然后继续执行程序。
- 如果 Result 是 Err:Err 就是整个函数的返回值,就像使用了 Return。
1 2 3 4 5 6 7 8 9 10
| use std::fs::File; use std::io; use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
|
?
语法糖会自动将错误类型转换为函数返回值需要的错误类型,实质上调用了 from 函数
3. 更进一步的链式调用
1 2 3 4 5 6 7 8 9
| use std::fs::File; use std::io; use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s) }
|