Rust实践总结

1. 语法

1.1. Rust中的r#""#

字符串字面值开头的r不是一个操作符,而是一个前缀。

在普通的字符串文字中,需要对某些字符进行转义才能使它们成为字符串的一部分,例如"\

  • 需要对"字符进行转义,否则它将终止字符串
  • 需要对\进行转义,因为它是转义字符

在Rust中,如果字符串中有需要转义的字符,可以在字符串前面加r前缀,然后放置任意数量的#符号,再用左引号"开始一个字符串。在结束该字符串时,再在右引号"之后放置与开头相同数量的#字符。这样,在""之间的字符串中就可以放置原始字符串,而不需要进行任何转义。例如:

1
r##"foo #"# bar"##

表示字符串foo #"# bar

字符串不会在中间的引号处停止,因为它后面只有一个#,而字符串是以两个#开始的。

1.2. ?运算符

参考 错误处理(Error Handling) 中 “2.4. 传播错误的简写:?运算符” 部分。

例:

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 mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
  • ?作用于Result类型:
    • 结尾调用?,如果结果是Ok,会返回Ok中的值
    • 如果结果是Err?会提早return,并返回Err中的值
  • ?作用于Option类型:
    • 如果值是SomeSome中的值作为表达式的返回值,同时函数继续
    • 如果值是NoneNone会从函数中提前返回
  • ?只能被用于函数的返回值与它作用的值相兼容的函数
    • 例如,上例中?作用于File::open("hello.txt")的返回值,即Result,那么整个函数的返回值类型也必须是Result才可以

2. Crates使用

2.1. serde

2.1.1. serde介绍

Rust中有一个99%的程序员或许都会用到的组件,那就是序列化组件: serde。

众所周知,rust是静态语言,这让我们在序列化上繁琐了很多,但是serde能帮助我们更好地序列化结构体,生产对应的数据。它实现了各种声明宏(declarative macros)以及过程宏(procedural macros)来协助我们序列化。

serde是Rust语言用来序列化和反序列化数据的一个非常高效的解决方案。本质上,serde提供了一个序列化层的概念。可以将任何支持的数据格式反序列化成中间格式,然后序列化成任何一种支持的数据格式。

同其他语言通过反射来实现序列化方案不同,serde基于Rust的trait系统来实现自己的序列化,每种数据结构通过实现serde的Serialize和Deserialize接口来实现序列化功能,这种实现方案可以有效避免反射的性能开销。Rust编译器可以在很多场景下对序列化进行高度优化,甚至可以达到一个资深工程师手写序列化代码的性能。

目前围绕serde支持的序列化的数据格式组件有:

  • JSON,许多HTTP API都使用的JavaScript对象符号。
  • Bincode,一个紧凑的二进制格式,用于伺服渲染的IPC引擎。
  • CBOR,一种简洁的二进制对象表示,专为小消息大小设计而不需要版本协商。
  • YAML,一个自称对人类友好的配置语言,其实不是标记语言。
  • MessagePack,一个高效的二进制格式,类似于一个紧凑的JSON。
  • TOML, Cargo使用的最小配置格式。
  • Pickle, Python世界中常见的格式。
  • RON,一个生锈的符号。
  • BSON, MongoDB使用的数据存储和网络传输格式。
  • Avro,在Apache Hadoop中使用的二进制格式,支持schema定义。
  • JSON5,一个JSON的超集,包括一些来自ES5的产品。
  • URL查询字符串,格式为x-www-form-urlencoded
  • Envy,一种将环境变量反序列化为锈蚀结构的方法。
  • Envy Store

反序列化:

  • Envy Store,一种反序列化AWS Parameter Store参数到Rust的方法结构体。(反序列化)
  • S-expressions, Lisp使用的代码和数据的文本表示形式语言的家庭。
  • D-Bus的二进制线格式。
  • FlexBuffers,谷歌的FlatBuffers 0 -copy的无模式表兄弟序列化格式。
  • Bencode,在BitTorrent协议中使用的一个简单的二进制格式。
  • DynamoDB Items, rusoto_dynamodb用来传输数据的格式和DynamoDB。
  • Hjson,一个JSON的语法扩展,围绕人类阅读和编辑而设计。(反序列化)

2.1.2. 使用derive(Serialize 和 Deserialize)

为了方便实现你自己创建的数据结构的序列化功能,serde提供了一个derive macro来自动生成你的数据结构的SerializeDeserialize trait的实现代码,让你定义的数据结构通过serde数据模型方便的表达出来。

你只需要在你代码里通过添加一行

1
#[derive(Serialize, Deserialize)]

serde就会自动生成这些实现代码。

此功能基于rust的#[derive]范式来实现,类似于你通过derive自动实现系统内置的CloneCopyDebug和其他的一些trait。它能够给绝大多数数据结构和枚举自动生成trait实现代码。极少数情况下,面对一些非常复杂的数据结构,你可能需要手工去实现这些代码。

2.1.3. #[derive(Serialize, Deserialize)]使用示例

Cargo.toml文件:

1
2
3
4
5
6
7
8
9
10
[package]
name = "try_serde"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = {version = "1.0.139", features = ["derive"]}
serde_json = "1.0.83"

src/main.rust文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: i32,
y: i32,
}

fn main() {
let point = Point { x: 1, y: 2 };

// Convert the Point to a JSON string.
let serialized = serde_json::to_string(&point).unwrap();

// Prints serialized = {"x":1,"y":2}
println!("serialized = {}", serialized);

// Convert the JSON string back to a Point.
let deserialized: Point = serde_json::from_str(&serialized).unwrap();

// Prints deserialized = Point { x: 1, y: 2 }
println!("deserialized = {:?}", deserialized);
}

cargo run运行结果:
serde用法示例运行结果

2.1.4. #[serde(default)]

#[serde(default)]用来设置字段的默认值。
示例:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Request {
// Use the result of a function as the default if "resource" is
// not included in the input
#[serde(default = "default_resource")]
resource: String,

// Use the type's implementation of std::default::Default if
// "timeout" is not included in the input.
#[serde(default)]
timeout: Timeout,

// Use a method from the type as the default if "priority" is not
// included in the input. This may also be a trait method.
#[serde(default = "Priority::lowest")]
priority: Priority,
}

fn default_resource() -> String {
"/".to_string()
}

/// Timeout in seconds.
#[derive(Deserialize, Debug)]
struct Timeout(u32);
impl Default for Timeout {
fn default() -> Self {
Timeout(30)
}
}

#[derive(Deserialize, Debug)]
enum Priority { ExtraHigh, High, Normal, Low, ExtraLow }
impl Priority {
fn lowest() -> Self { Priority::ExtraLow }
}

fn main() {
let json = r#"
[
{
"resource": "/users"
},
{
"timeout": 5,
"priority": "High"
}
]
"#;

let requests: Vec<Request> = serde_json::from_str(json).unwrap();

// The first request has resource="/users", timeout=30, priority=ExtraLow
println!("{:?}", requests[0]);

// The second request has resource="/", timeout=5, priority=High
println!("{:?}", requests[1]);
}

运行结果:
serde default示例

r#""#的说明见 Rust中的r#””# 部分。

2.2. config

GitHub链接:https://github.com/mehcode/config-rs
可用于从tomljsonyamlini等格式的文件中读取数据。示例:https://github.com/mehcode/config-rs/tree/master/examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use config::Config;
use std::collections::HashMap;

fn main() {
let settings = Config::builder()
// Add in `./Settings.toml`
.add_source(config::File::with_name("Settings.toml"))
// Add in settings from the environment (with a prefix of APP)
// Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
.add_source(config::Environment::with_prefix("APP"))
.build()
.unwrap();

// Print out our settings (as a HashMap)
println!(
"{:?}",
settings
.try_deserialize::<HashMap<String, String>>()
.unwrap()
);
}

Settings.toml内容:

1
2
3
debug = false
priority = 32
key = "189rjfadoisfj8923fjio"

Cargo.toml内容:

1
2
3
4
5
6
7
8
9
[package]
name = "try_config"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
config = "0.13.1"

目录结构:

1
2
3
4
5
try_config
|-------- src
|-------- main.rs
|-------- Cargo.toml
|-------- Settings.toml

cargo run的运行结果:
config读取toml文件

参考链接

[1] serde - rust的序列化方案:https://zhuanlan.zhihu.com/p/54004232
[2] 【rust】序列化框架serde:https://whiteccinn.github.io/2021/04/13/Rust语言/rust-序列化框架serde/
[3] Using derive:https://serde.rs/derive.html
[4] Default value for a field:https://serde.rs/attr-default.html
[5] Rust中的r#””#:https://cloud.tencent.com/developer/ask/sof/37840