Rust
参考
陈天 - Rust 编程第一课 (参考:https://github.com/tyr-rust-bootcamp)
唐刚 - Rust 语言从入门到实践
Rust 官方资源
翻译资源
掘金 - 基础语法快速总览参考:https://juejin.cn/post/7362023585517338664?share_token=b728f030-9efc-493d-8b3a-51a1b03a512f
前置
安装
rustup 是 rust 的编译工具,cargo 是 rust 的工具链
安装时会自动安装两个工具
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | zsh
安装输出的内容很详细,可以仔细看下
包管理
cargo --list # 查看所有命令
初始化项目
cargo new --bin 项目名 # --bin 创建二进制项目(不写,默认 --bin),--lib 创建一个库项目
# 目录
├── Cargo.lock # 锁文件
├── Cargo.toml # 依赖记录文件
└── src
└── main.rs # 入口
依赖管理
# 安装cargo-generate包(用来生成项目模板的工具,使用 github repo 生成项目)
# cargo 会自动下载源码编译为二进制文件到 ~/.cargo/bin 目录下
cargo install cargo-generate
# 调用了上一步骤下载的 cargo-generate
cargo generate tyr-rust-bootcamp/template # 以 https://github.com/tyr-rust-bootcamp/template 这个项目作为目模板
# 安装Cargo.toml记录的全部依赖
cargo fetch
# 更新依赖项到最新的兼容版本
cargo update
构建二进制文件
cargo build # 根目录/debug下。这种编译方式的产物中包含debug相关的标准库,所以体积比较大
cargo build --release # 根目录/release下
运行
cargo run
工程化
陈天的Rust模板 ,其中配置了一系列的工具,以及使用Github Action来定义提交代码的流程
print!打印
println!
是换行打印
基础用法
// 【占位符输出】
let name = "tom";
// ⚠️ {} 是占位符
print!("Name: {}", name); // Name: tom
println!("{name} "); // tom
---------------------------------------------
// 【根据变量位置输出】
println!("{1} 、 {0}", "Alice", "Bob"); // Bob 、 Alice
其他占位符
下面直接常用的占位符,更多请参考:https://rustwiki.org/zh-CN/std/fmt/index.html
基本占位符
{}
默认格式化输出,适用于实现了 Display
trait 的类型
调试输出
{:?}
用于调试输出,适用于实现了 Debug
trait 的类型。这对于查看复杂类型(如结构体、枚举等)的内容非常有用
#[derive(Debug)] // 特殊的宏,所以后面{:?}才能输出结构体内部数据
struct User {
username:String,
active: bool
}
let mut user = User{
username:String::from("jack"),
active:true
};
print!("{:?}",user) // User { username: "jack", active: true }
数字输出
{:b}
:二进制格式{:o}
:八进制格式{:x}
、{:X}
:十六进制格式(小写或大写)rustlet num = 15u32; println!("{:x?} ", num); // f println!("{:X?} ", num); // F
{:e}
、{:E}
:科学计数法表示(小写或大写)
指针输出
{:p}
:指针地址的格式化输出
变量与所有权
变量声明与赋值
定义变量
// 指定类型
let x:u32=10
let x=10u32
// 自动推导类型
let x=10
变量默认不可变
// 不可边变量,但是可以覆盖
let x=10
let x=20
// 可变变量
let mut y=10
所有权
变量持有值的所有权,遵循的规则:
- Rust 中,每一个值都有一个所有者
- 任何一个时刻,一个值只有一个所有者
- 当所有者所在作用域(scope)结束的时候,其管理的值会被一起释放掉
值是Rust管理的,我们的变量只是持有这些值。当作用域结束后,Rust自动释放
值复制(copy)
固定类型存储在栈上,赋值操作都是对值进行复制。变量各自持有一个值,与其他语言一致
所有的整数类型,比如 u32
布尔类型 bool
浮点数类型,比如 f32、f6
字符类型 char
由以上类型组成的元组类型 tuple,如(i32, i32, char)
由以上类型组成的数组类型 array,如 [9; 100]
不可变引用类型 &
fn main() {
let a = 10u32;
let b = a;
println!("{a}");
}
// 函数执行完毕后,值就被回收了
本质原因是上述类型都实现了 Copy trait
**所有权转移 (move) **
除去值复制的情况以外,都会发生所有权转移
fn main(){
// 可变类型存储在堆上,其赋值给 s2 后s1就失效 ,s1 处于无效状态
let s1=String::from("xxx");
let s2=s1;
print!("{s2}");
}
// 函数执行完毕后,值就被回收了
作用域:
函数调用传参(实参 -> 形参)
rustfn foo(s:String){ print!("{s}"); } fn main(){ let s1=String::from("xxx"); foo(s1); // 这里相当于 函数实参赋值给形参,也发生了所有权的转移 。 函数结束后值被销毁了 print!("{s}"); // ❌ 这里就会报错,s1的值被转移了,处于无效状态 } // 函数执行完毕后,值就被回收了
如何解决?采用下面方案或借用
变量接收函数返回值
rust// 正确写法 fn foo(s:String) -> String{ print!("{s}"); s } fn main(){ let s1=String::from("xxx"); let s1=foo(s1); // ✅ 函数将返回值转移回 s1 print!("{s}"); }
for 循环中,每一次循环是独立的,变量转移到第 1次循环中了
rustfn main() { let s = "I am a superman.".to_string(); for _ in 1..10 { let tmp_s = s; ❌ println!("s is {}", tmp_s); } } // 正确写法 let tmp_s = &s; // ✅ 仅仅发生复制
数组、元组子元素
rustfn main() { let arr = ["hello world".to_string(), "hello world".to_string()]; let s = arr[0]; // ❌ 为了保证数组可用,rust 中数组元素复制 。但是 String 没有实现 Copy trait,才报错 }
rustfn main() { let arr = ("hello world".to_string(), "hello world".to_string()); // 元组 let s = arr.0; // 转移 println!("{}", arr.0); // arr.1 可以正常打印,但打印 arr.0 和打印 arr 则报错,提示发生了移动 }
引用与借用
1、引用、借用的概念
&s1
表示对变量s1
的引用,使用&s1
的行为叫做借用变量s1
的所有权
2、引用的作用域
引用的作用域从声明的地方开始一直持续到最后一次使用为止
3、引用没有所有权(以只读引用的代码为例子,注意两种引用均遵循该原则)
函数入参是引用,不会发生所有权转移
&s1
是对s1
的引用,引用没有所有权,所以也不存在所有权转移。s1一直拥有所有权rustfn foo(s:&String){ println!("{s}"); // ✅ 正常输出了 xxx . ⚠️ 这里是 s 最后一次使用,即作用域结束的位置 ; 实际上,s是地址,println自动解引用获取了值 } fn main(){ let s1=String::from("xxx"); foo(&s1); // ⚠️ 多了 "&" println!("{}",s1); // ✅ 正常输出了 xxx }
函数返参数是引用,不会发生所有权转移。但是函数结束值被销毁,所有这种写法有语法错误(与生命周期的概念相关)
rustfn foo() -> &String { let s = String::from("hello"); // s 是一个新字符串 &s } // 这里 s 离开作用域并被丢弃。其内存被释放。 // 编译报错 ❌
rustfn foo() -> String { let s = String::from("hello"); s } fn main(){ let res= foo(); print!("{}",res); // ✅ 正常输出了 hello }
上面的例子保证了,引用只在作用域内有效,无法突破作用域的范围被开发者错误的使用
在具有指针的语言中,很容易出现:释放内存资源后,还有其他指针指向这个内存资源,出现悬垂指针(dangling pointer)。在 Rust 中编译器确保引用一定有效,不会变成悬垂状态
只读/不可变引用
同上面代码
fn foo(s:&String){
println!("{s}"); // 输出 xxx
}
fn main(){
let s1=String::from("xxx");
foo(&s1); // ⚠️ 多了 "&"
println!("{}",s1); // ✅ 正常输出了 xxx
}
规则
同一变量的可读引用 r 的作用域内可以存在其他可读引用 r1、r2...
同一变量的可读引用 r 的作用域内,不能有对该变量的写操作 或 其他可写引用
rustfn main() { let mut s = String::from("hello"); let r = &s; s.push_str(", world"); // ❌ 编译错误。 可读引用 r 的作用域内,出现了对原变量的写操作 print!("{}",r); } fn main() { let mut s = String::from("hello"); { let r = &s; } s.push_str(", world"); // ❌ 编译错误。 可读引用 r 的作用域内,出现了对原变量的写操作 print!("{}",r); }
rustfn main() { let mut s = String::from("hello"); let r = &s; let r2 = &mut s;; // ❌ 编译错误。 可读引用 r 的作用域内,出现了其他可写引用 print!("{}",r); }
可写引用
&mut s
表示这个引用是可写的
fn foo(s:&mut String){
// ⚠️ push_str是 结构体 String的方法,&String 调用时会自动解引用
s.push_str(", world"); // 输出 xxx, world
}
fn main(){
let mut s1=String::from("xxx");
foo(&mut s1);
println!("{}",s1);
}
规则
同一变量的某个可写引用 r ,在其作用域内不能有其他 可读、可写引用
错误用法:
同一变量的不同可写引用作用域冲突
rustfn main(){ let mut s1=String::from("xxx"); let r1=&mut s1; let r2=&mut s1; println!("{}", r1); // ❌ 编译错误。可写引用 r1 作用域内有其他可写引用 r2 }
rustfn main(){ let mut s1=String::from("xxx"); let r1=&mut s1; let r2=&mut s1; println!("{}", r2); // ✅ 编译通过。输出 xxx }
同一变量的可读引用作用域、可写引用作用域冲突
可读作用作用域之间不能出现可写引用
rustfn main() { let mut s = String::from("hello"); let r1 = &s; let r2 = &mut s; // 可读作用域之间,出现了可写引用 r2 println!("{}", r1); // ❌ 编译报错 }
可读作用作用域之间不能出现可写引用
rustfn main() { let mut s = String::from("hello"); let r2 = &mut s; let r1 = &s; // 可写作用域之间,出现了可读引用 r1 println!("{}", r2); // ❌ 编译报错 }
写
解引用后才能读取值,才能重写值
fn main() {
// ⚠️ 必须保证多级引用都是可变引用,最后解出来才能重写值
let mut a="a".to_string();
let mut b =&mut a; // 变量a声明为mut ,取引用也要 &mut
let c =&mut b; // ⚠️ c 存的是可变引用 b,如果c 不再取 &c,c 的 mut 修饰符加不加都行
// 引用只是内存地址,*可解引用获取值,注意对应层数
println!("{}",**c); // 输出 a
**c="c".to_string(); // 修改
println!("{}",**c); // 输出 c
}
自定解引用
这些限制的好处是 Rust 可以在编译时就避免数据竞争,同一作用域只能有 1 个可读引用可以修改值
推荐
通过括号创造作用域,如果仅仅通过引用出现的最后一行作为其作用域结尾,当逻辑很复杂时根本看不出来每个引用的作用域。可以使用括号明确的划分作用域
fn main() {
let mut s = String::from("hello");
{
let r2 = &mut s;
println!("{}", r2);
}
let r1 = &s; // 可写作用域之间,出现了可读引用 r1
}
类型系统
标量类型(Scalar Types)
整数类型
- 有符号整数:
i8
,i16
,i32
,i64
,i128
,isize
- 无符号整数:
u8
,u16
,u32
,u64
,u128
,usize
其中,isize
和 usize
的大小取决于运行程序的计算机架构(32位或64位)。
浮点数类型
f32
: 32位单精度浮点数f64
: 64位双精度浮点数,默认使用这个类型因为现代CPU对它的支持非常好。
布尔类型
bool
: 只能取值为 true
或 false
字符类型
char
: 表示一个Unicode标量值,是四个字节长度的字符
fn main(){
let c:char='你';
}
复合类型(Compound Types)
元组
固定长度,元素可以是不同类型
let x=(1,1,1) // 自定推导类型
let x:(u32,u32,u32)=(1,1,1) // 带类型声明
数组、切片、向量
数组:固定长度,元素只能是一种类型(与 Go 一致)
let arr = [1, 2, 3, 4, 5]; // 自定推导类型
let arr:[i32;5] = [1, 2, 3, 4, 5]; // 带类型声明
切片:对数组、字符串的一部分引用(范围是左闭右开)。⚠️ 与 Go 中切片不同,Rust 中切片只能是数组的局部视图,不能超过数组范围,Go 中的切片支持自动扩容
// 从数组生成切片
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3];
let slice:&[i32] = &arr[1..3]; // 带类型声明
// 从字符串生成切片
let s = "abcdef";
let s1 = &s[1..3];
let slice:&str = &s[1..3]; // 带类型声明
// 从切片生成切片
let slice1 = &slice[1..3];
向量:也叫动态数组
Vec<User>
字符串
⚠️ Rust 中字符串无法通过角标索引
Rust把字符串在各种场景下的使用给模型化、抽象化成下面几种类型
&'static str, String, &String, str, &str,
[u8], &[u8], &[u8; N], Vec<u8>
OsStr, OsString
Path, PathBuf
CStr, CString
字符串
fn main(){
// 字符串字面量存储在静态数据区,类型是 &'static str
// ⚠️ &'static str 是特殊的&str,所以&'static str标记为 &str 也没问题,反之不可以
let s1: &'static str = "I am a superman.";
// String与字面量的区别是 String 拥有所有权、容量可增长、分配在堆上的特征
// xxx.to_string()、String::from(xxx) 两种方式将 字面量、&String、&str(切片引用)转为 String,这个过程会将字面量拷贝到堆上
let s2: String = s1.to_string();
// String 的引用类型
let s3: &String = &s2;
// &str 切片类型引用,也等价于s2.as_str()
// &'static str、String、&String 、&Str 都支持取切片
let s4: &str = &s2[..]; // 字符串的切片引用类型
let s5: &str = &s2[..6];// 字符串的切片引用类型,范围 [0,6)
}
字节串
[u8] // 是字节串切片,大小是可以动态变化的
&[u8] // 是对字节串切片的引用,即切片引用,与 &str 是类似的
[u8; N] // 字节数组
// 获取字节数组引用=>
// 方式 1:let arr: [u8; 3] = [0, 1, 2,]; let slice: &[u8] = &arr[1..2];
// 方式 2:let arr: [u8; 3] = [0, 1, 2,]; let slice: &[u8] = arr.as_bytes();
&[u8; N] // 字节数组(N是字节数组的长度)的引用
Vec<u8> // 是 u8 类型的动态数组。与 String 类似,这是一种具有所有权的类型
引用 --> 引用切片
这个转换是隐式转换
字符串
fn main(){
let s="xxx".to_string(); // s应该是 String 类型
let s1 = &s;
let s2:&str=&s; // 如果 s2 是、指定切片引用类型,Rust 会发生隐式转换
// 使用 &str 就能让函数接收 &String、&str 两种类型
fn receive(s:&str){
// 逻辑
println!("{}", s);
}
receive(&s1);
receive(s2);
// ⚠️ 这意味着在 Rust 中 &String、&str 是可以混用的
}
向量与向量切片
// 参数接收 &Vec<T>向量、&[T]向量切片
fn receive(s:&[T]){
}
// 参数接收 &Vec<u8>、&[u8]向量切片 (T 是 u8时,就是字节串)
fn receive(s:&[u8]){
}
字节串 --> 字符串
字符串 --> 字符串数组
fn main(){
let s1="你好".to_string();
let char_vec: Vec<char>=s1.chars().collect(); // chars() 可以用来把字符串转换为一个迭代器,collect() 将迭代器所有元素收集到 Vec 中
println!("{:?}",char_vec); // ['你', '好']
// for 循环迭代器
for ch in s.chars() {
println!("{:?}", ch);
}
}
结构体
普通结构体
// 【声明】
// ⚠️ Go 是 type User struct{}
struct User {
username:String,
active: bool
} // ⚠️ 无需分号
---------------------------------------------
// 【实例化】
let username = String::from("tom");
let active = true;
struct User {
username:String,
active: bool
}
// 字面量实例化、支持简写方式,这些类似 JS
let user1 = User{
username, // 简写
active // 简写
};
// 也支持展开实例字段,类似 JS
let user2 = User{
username:String::from("jack"),
..user1 // ⚠️ 展开结构体实例被称为 base struct , 必须在最后,这与 JS 不同
};
---------------------------------------------
// 【修改实例】注意声明为 mut
let username = String::from("tom");
let active = true;
#[derive(Debug)] // 特殊的宏,所以后面{:?}才能输出结构体内部数据
struct User {
username:String,
active: bool
}
let mut user = User{
username,
active
};
// ⚠️ 修改实例,变量前需加 mut
user.username=String::from("jack");
print!("{:?}",user) // User { username: "jack", active: true }
元组结构体
元组结构体更加紧凑
// 普通结构体
struct Point{
x:u32,
y:u32,
z:u32
}
// ---------------------------------
// 可以看下元组结构体,确实更加紧凑
// 【声明】
struct Point(u32, u32, u32);
// 【实例化】
let origin = Point(0, 0, 0);
单元结构体
没有任何字段的结构体
struct ArticleModule; // ⚠️ 没有字段可省略 {}
let module = ArticleModule; // 【实例化】 没有字段,可以省略{}
这个语法有歧义,很容易让人疑惑:类型为什么能直接赋给一个变量?
实际上是单元结构体省略了
方法、关联方法
在 Rust 中 Struct 实例可以调用方法、关联方法,⚠️ 实例引用调用时会自动解引用,所以也可调用
user.get_name()
&user.get_name() // ✅ &user会解引用,所以也可调用get_name
方法
相当类的实例方法,通过.
调用
// 声明结构体
#[derive(Debug)]
struct User{
name:String,
age:u8,
}
// 实例方法
impl User {
// 1、命名推荐下划线
// 2、函数最后一句表达式,作为返回值。⚠️ 不加分号,加了分号就是"语句"
// 3、参数类型 Self ,表示自身实例
// 🚩 方式 1
fn get_name(self) -> String { // 参数是 self:Self 的简写。会发生所有权转移
self.name
}
// 🚩 方式 2
fn get_name(&self) -> String {// 参数是 self:&Self 的简写。可读引用,只能克隆值作为返回值
self.name.clone()
}
// 🚩 方式 3
fn get_name(&mut self) -> String {// 参数是 self:&mut Self 的简写。通过可写引用,可操作实例
self.name=""
self.name.clone()
}
}
🚩 方式 1 案例
// 声明结构体
#[derive(Debug)]
struct User{
name:String,
age:u8,
}
// 实例方法
impl User {
fn get_name(self) -> String {// 所有权转
self.name
}
}
fn main(){
let user= User{
name:String::from("tom"),
age:20
};
let name = user.get_name(); // User实例的所有权会转移到形参,实例不可再用
print!("{:?}",user); // ❌ 编译不通过 user 所有权已被转移
}
🚩 方式 3 案例
// 声明结构体
#[derive(Debug)]
struct User{
name:String,
age:u8,
}
// 实例方法
impl User {
fn get_name(&mut self) -> String {// 参数是 self:&mut Self 的简写
self.name="变更".to_string();
self.name.clone()
}
}
fn main() {
// user 也要标记为 mut
let mut user= User{
name:String::from("tom"),
age:20
};
print!("{}",user.get_name()); // 变更
print!("{:?}",user); // User { name: "变更", age: 20 }
}
关联方法
相当类的静态方法,通过::
调用
// 声明结构体
struct User{
name:String,
age:u8,
}
// 关联方法
impl User {
// 1、Rust 中认为参数不含有self ,就是关联方法
// 2、习惯上,结构体都会定义 new 方法,用来实例化
fn new(name:String,age:u8) -> User { // 参数是 self:Self 的简写,所有权会转移到形参
User {name,age} // ⚠️ 隐式返回,最后一行是表达式(不要加分号),其值最为返回值
}
}
fn main() {
// 调用使用::
let user = User::new(String::from("Alice"), 30);
}
pub修饰符
在 Rust 中,struct
的方法、关联方法都可以使用 pub
关键字来标记为公共的,这些函数可以从定义它们的模块外部访问
所有权
转移
let user1 = User{
username:String::from("tom"),
active:true
};
let user2=user1 // 所有权发生转移
---------------------------------------------
#[derive(Debug)]
struct User{
name:String,
age:u8,
}
impl User {
fn get_name(self) -> String {
self.name
}
}
let user= User{
name:String::from("tom"),
age:20
};
let x=user.get_name(); // 所有权转移
print!("{:?}",user); // ❌ 编译报错
部分(Partial Move)
结构体是多字段组成的类型,可以发生某字段的转移
let user1 = User{
username:String::from("tom"),
active:true
};
let name = user1.username // String是所有权类型,会发生所有权发生转移
print!("{:?}",user) // ❌ 编译报错
枚举
变体
Rust中的变体指的是枚举项
enum Color{
Red,
Green,
Blue
}
// 使用枚举的一个变体
// Color 是类型
// Color::Red 是值
let my_color:Color = Color::Red;
变体可以指定实际数据
- 与其他语言不同,Rust中数据类型只能是 isize 类型
- 不指定时,默认 Red 为 0,后续从 0 开始。如果中间某项指定数值 X,则后续从X 开始
enum Color{
Red, // 0
Blue, // 1
Black=10,
Yellow // 11
}
fn main() {
println!("{}",Color::Blue as isize);
println!("{}",Color::Yellow as isize);
}
带负载的变体
当指定为某一个变体时,如果该变体带负载(类型信息),需要实例化该变体
enum Shape {
Rectangle { width:u32,height:u32 }, // 矩形, 负载是结构体 ,使用{ }
Triangle ((u32,u32),(u32,u32),(u32,u32)),// 三角形, 负载是元组 ,使用()
Circle { origin:(u32,u32), radius:u32 }, // 圆形,负载是 结构体
}
// 带负载的枚举,需要实例化数据
fn main(){
let shape = Shape::Rectangle {width:10,height:20};
}
自动引入变体
函数签名中使用枚举,函数作用域内自动引入
use crate::Gender::{Man, Woman};
#[derive(Debug)]
enum Gender{
Man,
Woman
}
fn main() {
fn deal(value:u8) ->Gender {
if(value==0){
return Man; // ⚠️ 作用域内,可直接使用变体
}
return Woman; // ⚠️ 作用域内,可直接使用变体
}
println!("{:?}",deal(0)) // 加上#[derive(Debug)],这里输出的就是变体名,即 Man
}
模式匹配作用域中也会自动引入(后面会讲到)
let res: Result<i32, &str> = Ok(42);
// 模式匹配
match res {
Ok(value) => println!("Success: {}", value), // 直接使用 Ok
Err(e) => println!("Error: {}", e), // 直接使用 Err
}
解构带负载的枚举
let res: Result<i32, &str> = Ok(100);
// 使用解构的方式
if let Ok(value) = res {
println!("Got a value: {}", value);
} else {
println!("Error");
}
// --------------------------------
enum Gender{
Man,
Woman
}
let user = Gender::Man;
if let Man=user{ // ❌ 不带负载的不行,这种一般认为是判断是否相等,而不是解构
println!("男")
}else{
print!("女");
}
枚举的方法
当变量赋值为某一变体后,该变量可调用枚举的方法
enum MyEnum {
Add,
Subtract,
}
impl MyEnum {
fn run(&self, x: i32, y: i32) -> i32 { // &self 是 self:&Self 的简写
// match 语句
match self {
Self::Add => x + y,
Self::Subtract => x - y,
}
}
}
fn main() {
// 实例化枚举
let add = MyEnum::Add;
// 实例化后执行枚举的方法
add.run(100, 200);
}
Option枚举
Option 是内置的枚举,表示区分变量的零值、正常值
补充:
在 JS 中,以 null、undefined 作为空的概念。但是这样极容易出现空指针异常
后来出现了类似 Go的方式,每个类型都有默认零值表示空的概念,如果一个数字类型的值是 0,你根本无法区分是默认值还是实际值
Rust 采用的方式: 1、所有的变量定义后使用前都必须初始化; 2、把每个类型的零值单独提出来用枚举值
Option<T>::None
表示
// 源码
enum Option<T> {
Some(T), // 负载是元组
None,
}
// 下面两个都是 GO 中的零值,但是在 Rust 中是可以区分开的
let value1: Option<i32> = Some(0);
let value2: Option<i32> = Some("");
// 零值
let value3=Option::None
Result枚举
Result 也是内置的枚举,用于函数返回值类型,包含函数成功、出错两种状态,它包含Ok
或Err
两个变体
// 源码
enum Result<T,E>{
Ok(T), // 负载是元组
Err(E) // 负载是元组
}
// 应用
let res:Result<i32, &'static str>=Ok(100);
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
Err("Division by zero!") // 自动引入枚举变体,所以可以直接使用 Err
} else {
Ok(a / b)
}
}
别名
type
type 可以给类型起别名
type xx = Vec<String>; // ⚠️ 这是个语句,加分号
Newtype模式
通过只有一个元素的元组结构体,对基础类型进行封装
**例如:用户 ID 和订单 ID,其数据类型都是 u64 **
// 别名:
type UserId = u64;
type OrderId = u64;
// Newtype :
struct UserId(u64);
struct OrderId(u64);
// 在使用时,UserId、OrderId 比 u64 更具有语义化
fn process_user(user_id: UserId) {
}
// 🚩 ------------------------
// Newtype相比别名,其会屏蔽内部类型的方法、属性;同时,我们可以为 Newtype 类型实现新的方法
泛型
Rust 不支持设置泛型默认值,必须显示的赋值
结构体泛型
turbofish 语法传入泛型
结构体泛型
struct Point<T>{
x:T,
y:T
}
fn main() {
// 显式指定泛型 (⚠️ 与其他语言不同,显示指定泛型需要直接标记到 变量上)
let p1:Point<f32> = Point{x:1.0,y:2.0};
// 结构体名::<T>、函数名::<T> ,这种显示指定泛型的语法,称为 turbofish 语法
let p2 = Point::<i32>{x:1,y:2};
// 通过第一个类型为 T 的变量,推断出 T 的值
let p3 = Point{x:1,y:2}; // 推断为 i32
let p4 = Point{x:1.0,y:2.0}; // 推断为 f64
}
// ---------------------------------------------
// 元组结构体
struct Point<T>(T,T)
结构体方法、关联方法使用泛型
struct Wrapper<T> {
item: T,
}
// impl<T> 这个 T 在 impl block 域中定义了泛型T ,后面是用的 T 都是这里定义的
impl<T> Wrapper<T> {
fn new(item: T) -> Self {
Wrapper { item }
}
fn get_item(&self) -> &T {
&self.item
}
}
fn main() {
// 默认根据 item 值推导出 T 为 &str 类型
let w = Wrapper::new("Hello, world!");
// turbofish 语法,通过 Wrapper::<&str> 指定泛型 T 是&str
let w = Wrapper::<&str>::new("Hello, world!");
println!("{}", w.get_item());
}
函数泛型
turbofish 语法传入泛型
fn print_type<T>(item: T) {
println!("{:?}", item);
}
fn main() {
print_type::<i32>(42);
}
枚举泛型
前面提到过的 Result,直接将泛型传递给变体
enum Result<T,E>{
Ok(T), // 返回成功的类型 T
Err(E) // 返回失败的类型 E
}
type xxx = Result<i32, &'static str>
别名泛型
type<T> = Vec<T>
类型转换
as 语法
数值类型之间
类型不同无法运算 , 可使用 as 做类型强制转换
fn main(){
let a = 5u32;
let b = 10u8;
let c =a+b as u32;
println!("{}",c); // 15
let d =a as u8+b;
println!("{}",d); // 15
}
从高精度类型 ---> 低精度类型,会造成数据精度丢失
fn main(){
let a = 5.78f32;
println!("{}",a as i32); // 5 ⚠️ 精度丢失了
}
枚举类型 --> 整数
enum Status {
Active = 1,
Inactive = 2,
}
fn main() {
let status = Status::Active;
let num = status as i32; // 结果是 1
}
字符串 <--> 数值类型
字符串 --> 数值类型
fn main() {
let str_num = "123";
match str_num.parse::<i8>() { // ::<i8> 是 turbofish 指定函数传入的泛型 , 用于指定 num 的类型
Ok(num) => println!("转换成功: {}", num),
Err(e) => println!("转换失败: {}", e),
}
}
// str_num = "111aa" 这种会转换失败
// str_num = "111.11" 这种由于指定的泛型是 i8,所以也会失败
⚠️ 这是 Rust 中很常用的形式,函数返回 Result 枚举,直接将其放到 match 中处理成功、失败两种可能
控制流
条件
fn main() {
let number = 3;
if number < 5 {
} else if(number>=5 && number<10){
} else {
}
}
循环
也支持 break、continue
loop
一直循环
fn main() {
loop {
println!("again!");
}
}
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break xxx 支持作为 loop 的返回值
}
};
println!("The result is {result}");
}
while
for
for i in 0..5{
println!("{}", i); // 从 0 到 4
}
还可以用来是遍历迭代器
模式匹配
match
enum Color{
Blue,
Yellow,
Red,
Green,
Black
}
fn main() {
let my_color = Color::Red;
// match 有返回值
let ret=match my_color{
// 1、 多个条件用 | 拼接
Color::Blue|Color::Yellow => "blue或yellow",
// 2、 处理语句多行时,需要放在{}作用域内
Color::Red => "green", // 这可千万别当场 JS 的监听函数返回值。而是函数的最后一个表达式就是返回值
Color::Green => {
"red"
},
// 3、Rust 强制必须把枚举的所有分支都写出来,如何需要省略就用 _
_ => {
"未知"
}
}; // 一旦赋值就是语句了,加分号
println!("{}",ret);
}
match 带变体的枚举
enum Shape {
Rectangle { width:u32,height:u32 }, // 矩形, 负载是结构体 ,使用{ }
Triangle ((u32,u32),(u32,u32),(u32,u32)),// 三角形, 负载是元组 ,使用()
Circle { origin:(u32,u32), radius:u32 }, // 圆形,负载是 结构体
}
fn main(){
// let shape = Shape::Rectangle {width:10,height:20};
let shape = Shape::Circle {origin:(0,0),radius:20};
match shape{
Shape::Rectangle {width:120,height:20} =>{ // 必须与实例化数据一致才能匹配上
println!("命中")
}
Shape::Circle {origin,radius} =>{ // 解构构(类似 JS 中的解构)
println!("命中 origin:{:?} radius:{}",origin,radius) // 输出 命中 origin:(0, 0) radius:20
}
// 还可以将更深层的内容解开
//Shape::Circle {origin:(x,y),radius} =>{ //
// println!("命中 origin:({},{}) radius:{}",x,y,radius) // 输出 命中 origin:(0, 0) radius:20
//}
_=>{
println!("未命中") // 输出 未命中
}
}
}
match 还支持数字类型
fn main() {
let number = 13;
// 你可以试着修改上面的数字值,看看下面走哪个分支
println!("Tell me about {}", number);
match number {
// 匹配单个数字
1 => println!("One!"),
// 匹配几个数字
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// 匹配一个范围,左闭右闭区间
13..=19 => println!("A teen"),
// 处理剩下的情况
_ => println!("Ain't special"),
}
}
if let、while let
enum Color{
Blue,
Yellow,
Red,
Green,
}
fn main() {
let my_color = Color::Red;
//⚠️ if 、while 语法中 不能直接通过 == 判断分支,必须 let xxx = 枚举变体
if let Color::Red = my_color{ // 等号右边是值
println!("{}",my_color as isize);
};
}
解构
解构带负载的枚举
如果是有负载的变体,let 能匹配出来负载值。JS 中这种语法叫解构赋值
⚠️ 与上面提到的 if let、if while 也可以理解成解构,不带负载时就是对比是否相等
enum Shape {
Rectangle { width:u32,height:u32 },
Triangle ((u32,u32),(u32,u32),(u32,u32)),
Circle { origin:(u32,u32), radius:u32 },
}
fn main() {
let shape_a = Shape::Rectangle {width: 10, height: 20};
let shape_b = Shape::Circle {origin: (0,0), radius: 20};
// 方式 1
if let Shape::Rectangle {width, height} = shape_a {
println!("{}, {}", width, height); // 10 20
}else{ // 解构失败
panic!("Can't extract rectangle.");
}
// 方式 2
let Shape::Rectangle {width, height} = shape_a else{
panic!("Can't extract rectangle.");
};
}
解构元组
fn main() {
let point=(0,0,0);
let (x,y,z)=point;
println!("{},{},{}",x,y,z);
}
解构结构体
⚠️ 可能会导致,部分字段所有权转移
struct User{
name:String,
age:u8,
}
fn main() {
let user=User{
name:"jack".to_string(),
age:20
};
let User{name,age} = user;
println!("{}、{}",name,age); // jack 、 20
println!("{}",user.age); // 20
println!("{}",user.name); // ❌ 编译失败。 name 是 String 会发生 所有权部分移动(partially moved),所以 user 中的 name 不再可用
}
ref
struct User{
name:String,
age:u8,
}
fn main() {
let user=User{
name:"jack".to_string(),
age:20
};
// ref 告诉编译器不需要所有权转移, 自动获取的是借用,所以 name 实际类型是 &String
let User{ ref name, age} = user;
// let User{ ref mut name, age} = user; mut name花
println!("{}、{}",name,age); // jack 、 20
println!("{}",user.age); // 20
println!("{}",user.name); // jack
}
ref mut 通过引用修改
struct User{
name:String,
age:u8,
}
fn main() {
let mut user=User{
name:"jack".to_string(),
age:20
};
// ref 告诉编译器不需要所有权转移, 自动获取的是借用,所以 name 实际类型是 &String
let User{ ref mut name,age} = user;
let mut new_name="tom".to_string();
*name="tom".to_string();
println!("{}",user.age); // 20
println!("{}",user.name); // tom
}
解构函数参数
解构元组参数
fn deal((x,y,z):(u32,u32,u32)){
println!("{},{},{}",x,y,z); // jack,20
}
fn main() {
deal((1,2,3)) // 1,2,3
}
析构结构体参数
struct User{
name:String,
age:u8,
}
fn deal(User{ name,age }:&User){
println!("{},{}",name,age); // jack,20
}
fn main() {
let user=User{
name:"jack".to_string(),
age:20
};
deal(&user)
}