- 安装、vscode插件
- struct/enum数据结构
- 模式匹配、错误跳转、错误处理、异步处理
- 使用
mod
、crate
和worksapce
组织代码 - feature的作用
- 技巧汇总
开始
- 安装
export RUSTUP_DIST_SERVER=https://mirrors.sjtug.sjtu.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- vscode的插件
- rust-analyzer
- rust syntax
- crates:帮助你分析当前项目的依赖是否是最新的版本
- better toml:高亮 toml 语法
- rust test lens:可以帮你快速运行某个 Rust 测试
- Tabnine:基于 AI 的自动补全
- error lens: 显示错误
- cargo
Rust 使用名为 cargo 的工具来管理项目,做依赖管理以及开发过程中的任务管理,也是包管理工具
基础语法
函数类型
f: fn(i32) -> i32
数据结构
- 常量
- 常量是一个右值
- 编译后放在数据段
- 静态变量
- 静态变量也放在数据段
- 不是右值
sturct
结构体- 空结构体:不占空间
struct Marker
- 元组结构体:类似python的list:
struct Color(u8, u8, u8)
- 普通结构体:带名称
- 空结构体:不占空间
enum
:枚举与联合-
C枚举体
enum G { a = 0, b = 1, c = 2 }
-
rust的标签联合体
enum Event { Join((UserId, TopicId)), Leave((UserId, TopicId)), Message((UserId, TopicId, String)),}
-
用 struct
定义结构体,用 enum
定义标签联合体(tagged union)
Clone
和Copy
- Copy / Clone 两个派生宏
- Clone 让数据结构可以被复制
- 而 Copy 则让数据结构可以在参数传递的时候自动按字节拷贝
模式匹配
- 用于 struct / enum 中匹配部分或者全部内容
if/while let pat = expr {}
是match
的简写,用于只关心某种模式匹配的情况
错误跳转?
expr?
:当错误发生时退出函数,并返回错误
异步处理
expr.await
:执行async
函数直到完成。当前上下文可能被阻塞多次 socket.write(data).await?;
错误处理
- 借鉴 Haskell,把错误封装在
Result<T, E>
类型中T
是成功执行返回的结果类型E
代表错误类型
- 用
?
操作符传播错误
fn main() {
// ...
let body = reqwest::blocking::get(url).unwrap().text().unwrap();
}
unwrap()
方法抽取成功的结果,如果出错则终止
要传播错误,把unwrap()
换成?
操作符,并让main()
返回Result<T, E>
:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let body = reqwest::blocking::get(url)?.text()?;
Ok(())
}
项目的组织
rust用mod
组织代码
- 引入包
- 一个一个引入
- 在项目的入口文件
lib.rs/main.rs
中,用mod
声明要引入的文件
- 在项目的入口文件
- 一次引入目录下的所有文件
- 把所有要引入的文件放在一个目录下
- 在目录中创建
mod.rs
- 在
mod.rs
中引入其他文件 - 在
lib.rs/main.rs
中用mod + 目录名
- 在目录中创建
- 把所有要引入的文件放在一个目录下
- 一个一个引入
- 一个项目称为一个
crate
- 测试
- 单元测试
- 测试代码和代码放在相同文件中
-
测试代码用条件编译
#[cfg(test)]
修饰#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2+2, 4); } }
- 集成测试
- 放在
tests
目录下 - 只能测试
crate
的公开接口 - 会编译成单独的可执行文件
- 使用
cargo test
执行集成测试
- 放在
- 单元测试
workspace
- 作用
crate
代码太多,任何修改会重新编译整个crate
workspace
包含多个crate
- 修改代码时,只有涉及的
crate
会重新编译
- 修改代码时,只有涉及的
- 使用
- 在目录下生成
Cargo.toml
-
包含所有
crate
- 在目录下生成
- 作用
feature
feature 用作条件编译,你可以根据需要选择使用库的某些 feature。它的好处是可以让编译出的二进制比较灵活,根据需要装入不同的功能。在 docs.rs 下的某个库的文档中,你可以看到它都有哪些 feature。
定义 feature,你可以看 cargo book:https://doc.rust-lang.org/cargo/reference/features.html。下面是一个简单的例子:
在 cargo.toml 中,可以定义:
[features]
filter = ["futures-util"] // 定义 filter feature,它有额外对 futures-util 的依赖。
[dependencies]
futures-util = { version = "0.3", optional = true } // 这个 dep 声明成 optional
在 lib.rs 中:
#[cfg(feature = "filter")]
pub mod filter_all; // 只有编译 feature filter 时,才引入 mod feature_all 编译
技巧
打印类型的大小
println!("sizeof Result<String, ()>: {}", size_of::<Result<String, ()>>());
sizeof Result<String, ()>: 24
优化思路应该是跟Option<T>
类似。Result<String, ()>
的false case是(),就相当于是Option<String>
,可以用String里的ptr的值来实现零成本抽象
获取命令行参数
let args = std::env::args().collect::<Vec<String>>();
if let [_path, url, output, ..] = args.as_slice() {
println!("url: {}, output: {}", url, output);
} else {
println!("参数缺失");
}
运行工程
cargo run -- post httpbin.org/post a=1 b=2
--
就相当于target/debug/httpie
运行example
-
配置
[[example]] name = "exp" path = "example/exp.rs"
-
代码
// example/exp.rs fn main() { // 代码 }
-
运行
cargo run --example exp
运行bin
-
配置
[[bin]] name = "main" path = "src/main.rs"
-
代码
// src/main.rs fn main() { // 代码 }
-
运行
cargo run --bin main
web 相关知识的学习:
-
HTTP 协议需要基本了解(请求和响应长什么样子,都有什么方法(GET, POST, PUT, DELETE, PATCH, HEAD 等),HTTP 头(比如 content-type),状态码,MIME 类型,如何做 content negotiation 等)
-
Request / Response 模型需要了解
-
一般的 web 框架的路由结构,middleware 结构和并发处理模型需要了解 建议了解 http 协议后,简单看某个 web 框架的文档,比如 expressjs:https://expressjs.com/en/guide/routing.html
查看错误代码的详细说明
rustc --explain E0382
实现一个RawBuff类型
use std::{fmt, slice};
#[derive(Clone, Copy)]
struct RawBuf {
ptr: *mut u8,
len: usize,
}
impl From<Vec<u8>> for RawBuf {
fn from(vec: Vec<u8>) -> Self {
let slice = vec.into_boxed_slice();
Self {
len: slice.len(),
ptr: Box::into_raw(slice) as *mut u8,
}
}
}
impl fmt::Debug for RawBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let data = self.as_ref();
write!(f, "{:p}: {:?}", self.ptr, data)
}
}
impl AsRef<[u8]> for RawBuf {
fn as_ref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
fn main() {
let data = vec![1, 2, 3];
let buf: RawBuf = data.into();
use_buf(buf);
println!("buf: {:?}", buf);
}
fn use_buf(buf: RawBuf) {
println!("buf to die: {:?}", buf);
drop(buf);
}
impl Drop for RawBuf {
fn drop(&mut self) {
println!("drop");
}
}
as_ref, as_ptr
as_ref
拿到的是栈的指针as_ptr
拿到堆指针Vec::into_boxed_slice()
, 然后再用Box::into_raw
拿到裸指针
不会越界的数组index
let len = self.len();
idx = (len + idx % len) % len