Rust tips #1 ~ #20

Tip #001

Rust 不支持静态 vec(static vec),但是最接近的是静态数组。例如,如果你想存储三个字符串的数组,可以尝试这样: static STRINGS : [&str;3] = ["a", "b", "c"]

Tip #002

什么是可选值(optional)和 unwrap()? 可以将可选值想象成一个信封,它可以包含一个值(Some(item))或者什么都没有(None)。对可选值调用 unwrap() 要么返回包含的值,要么如果可选值是 None 的话就会使程序panic。

Tip #003

关于可选值( optional)的安全解包方式:

  • 使用 match 语句明确处理不同情况
  • unwrap_or_default: 要么解包得到值,要么返回默认值
  • unwrap_or_else: 允许你指定一个函数来处理 None/Error 解包结果

Tip #004

如果你没有时间完成特定的一段代码,但仍然希望程序可以编译,可以考虑使用 todo!()unimplemented!() 宏。你的代码会继续编译通过,但如果程序运行到包含这些宏的代码块中,它将会panic。

todo! 更适合临时标记,而 unimplemented! 则更适合长期未实现的情况。

Tip #005

如果你想测试一个枚举类型的实例是否符合枚举的特定变体,你可以使用 matches! 宏,例如:
let match_res = matches!(my_variable, enum_type);

你也可以匹配其他模式,如范围,例如: matches!(foo, 'A'..='Z')

Tip #006

你知道吗, {} 块可以像函数一样返回结果?这使得基于条件的赋值变得非常容易。例如:

1
2
3
4
5
6
7
let car_ready = {
start_engine();
match engine_state {
Engine::running => true,
Engine::error => false
}
}

Tip #007

const和static之间有什么区别?

  • const值在编译期间会被替换("内联")到你的代码中。这对于在代码的各个地方使用的常量值来说是理想的。
  • static值在运行时有一个内存地址,并且会在整个应用程序的生命周期中存在。它们可以是可变的。如果你需要一个固定的位置来存放共享资源,例如硬件驱动程序或数据库接口,那么静态变量就很有用。

Tip #008

如果你想将几个相同或不同类型的值一起存储,元组类型就很有用。下面是一个将元组类型声明为结构体元组的示例,访问元组类型字段,以及从函数返回"匿名"元组类型:

1
2
3
4
5
6
struct MyTuple(String, i32);
fn flip(a: (i32, String)) -> MyTuple {
let my_tuple = MyTuple(String::from("the answer"), 42);
(a.1, a.0)
}

Tips #009

让我们来谈谈 string

在 Rust 中有两种基本的字符串类型: Stringstr

String: (也称为 Owned String),在堆上分配内存并且可变。String 在运行时使用,当你想要创建和修改字符串时。你可以将 String 作为 &str 引用传递给只读函数。

str: (也称为 String Slice) 是对一序列 UTF8 数据的引用。你可以在编译时以常量、静态字面量的形式创建 str,或者在运行时从 String 对象获取它们。str 总是不可变的。

Tips #010

拼接 string 有两种方式可以将两个字符串连接,分别使用:

  • push_str
  • format! 宏
1
2
3
4
5
6
7
8
9
// 使用 push_str
let mut first = String::from("Hello, ");
first.push_str("world!");
assert_eq!(first, "Hello, world!");
// 使用 format! 宏
let second = String::from("Hello, ");
let combined = format!("{}{}", second, "world!");
assert_eq!(combined, "Hello, world!");

Tips #011

格式化打印宏

  • print!println!: 在控制台打印文本。
    您可以使用格式字符串打印变量的内容,例如, let a: i32 = 1234; println!("The value of a is: {}", a); 将打印出 "The value of a is 1234"
  • eprintln!: 打印到标准错误流(stderr)
  • dbg!: 打印变量的值和行号,对于轻量级调试很有用。

Tips #012

可选值解包使用 if let() 类似于 Swift , Rust 允许我们使用 if let 来测试一个可选值是否有值,这种方式便于保持程序流程的简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let optional_value: Option<i32> = Some(9);
// 传统方式
match optional_value {
Some(value) => println!("The value is: {}", value),
None => println!("There is no value"),
}
// 使用 if let
if let Some(value) = optional_value {
println!("The value is: {}", value);
} else {
println!("There is no value");
}
}

Tips #013

使用 if let 解包多个可选值。继上一条 tip,如果你需要同时检查多个可选值是否都有值,你可以使用 if let 来测试和解包可选值的元组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let optional_tuple: (Option<i32>, Option<bool>) = (Some(5), Some(true));
// 传统方式
match optional_tuple {
(Some(int_value), Some(bool_value)) => {
println!("Received int: {} and bool: {}", int_value, bool_value);
}
_ => println!("One or more options were None"),
}
// 使用 if let
if let (Some(int_value), Some(bool_value)) = optional_tuple {
println!("Received int: {} and bool: {}", int_value, bool_value);
} else {
println!("One or more options were None");
}
}

Tips #014

实例化 vector
Vec是一种动态数组类型,非常适合存储相同数据类型的序列。下面是一些使用标准库调用和vec!宏实例化Vec的方式。注意类型是如何处理的:

1
2
3
4
5
6
7
8
9
10
11
12
// Type automatically inferred by the next push.
let mut a: Vec<i64> = Vec::new();
a.push(1_i64);
// Type set during instantiation.
let mut b: Vec<i64> = Vec::<i64>::new();
// Type defined explicitly, initialized using vec!
let mut c: Vec<i64> = vec![];
// Type automatically inferred, prefilled with vec!
let mut d: Vec<i64> = vec![1_i64, 2_i64, 3_i64];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 空Vec
let mut vec1: Vec<i32> = Vec::new();
// 用值初始化
let mut vec2 = vec![1, 2, 3];
// 用指定数量的元素和默认值初始化
let mut vec3 = vec![0; 5]; // vec3 = [0, 0, 0, 0, 0]
// 从迭代器获取元素
let vec4 = (0..5).collect();
let vec5: Vec<_> = ["foo", "bar"].into_iter().collect();
// 用给定的初始容量初始化
let mut vec6 = Vec::with_capacity(10);

Tip #015

Vector迭代。

你可以使用 vec.iter() 来对 Vector进行迭代,使用 map()for_each() 函数,这与例如 JavaScript 中的方式类似。

1
2
3
4
5
6
7
8
9
10
11
let numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
// Using for_each to print each element
numbers.iter().for_each(|&x: i32| {
println!("{}", x);
});
// Use map to transform a vector.
let squared: Vec<i32> = numbers.iter().map(|&x: i32| x * x).collect();
println!("{:?}", squared);

Tips #016

嘿,你有一些不错的HashMap,怎么初始化它们?除了创建一个新的HashMap并插入键值对之外,一种值得注意的方式是使用一个元组数组和一个#迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::HashMap;
fn main() {
// 从一个元组数组创建HashMap
let codes: HashMap<_, _> = [
(101, "abc"),
(102, "def"),
(103, "ghi"),
].iter().cloned().collect();
println!("codes = {:?}", codes);
}

Tips #017

更好的方式来unwrap()
如果内容是None的话,unwrap()一个可选值会导致panic。以下是在unwrap时处理None的一些方法:

  • unwrap_or_default(): 如果可选值是None,返回默认值。
  • unwrap_or(): 提供一个替代值。
    -unwrap_or_else(): 执行一个函数,如果可选值是None的话该函数返回某个值。
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let x: Option<i32> = None;
// 返回默认值0
println!("{}", x.unwrap_or_default());
// 返回提供的替代值99
println!("{}", x.unwrap_or(99));
// 返回函数提供的值
println!("{}", x.unwrap_or_else(|| "No value".to_string()));
}

Tips #018

Rust 允许我们轻松定义匿名函数(也称为lambdaclosure 或闭包)。闭包通常与 iterator 一起使用,或者用于定义回调函数。以下是语法示例:

1
2
3
4
5
6
7
8
9
10
fn main() {
let multiply = |a, b| a * b;
let product = multiply(3, 4);
println!("Product: {}", product); // 输出: Product: 12
let add_two = |x| x + 2;
let values = vec![1, 2, 3, 4, 5];
let new_values: Vec<_> = values.iter().map(add_two).collect();
println!("New values: {:?}", new_values); // 输出: New values: [3, 4, 5, 6, 7]
}

Tips #019

闭包中使用 move 可以从周围作用域捕获变量。这意味着闭包将获取这些变量的所有权,而无需传递任何参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
let num = 5;
let closure = || {
println!("Num: {}", num); // 错误: `num` 不可借用
};
let closure_move = move || {
println!("Num: {}", num); // 正确: `num` 被移动到闭包中
};
closure(); // 调用没有捕获变量的闭包
closure_move(); // 调用捕获了 `num` 的闭包
}

Tips #020

实现 From() trait 提供了一种在复杂类型之间进行转换的机制。Rust 还会自动为对应的类型提供一个 Into() 实现。在下面的示例中,我们为 ColorFloat 实现了 From,但是可以使用 Color8BitInto():

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
struct Color8Bit {
r: u8,
g: u8,
b: u8,
}
struct ColorFloat {
r: f32,
g: f32,
b: f32,
}
impl From<Color8Bit> for ColorFloat {
fn from(color: Color8Bit) -> Self {
ColorFloat {
r: (color.r as f32) / 255.0,
g: (color.g as f32) / 255.0,
b: (color.b as f32) / 255.0,
}
}
}
fn main() {
let color8 = Color8Bit { r: 230, g: 100, b: 50 };
// 使用 Into 特性将 Color8Bit 转换为 ColorFloat
let color_float: ColorFloat = color8.into();
println!("Color Float: {}, {}, {}", color_float.r, color_float.g, color_float.b);
}

整理自 Daily Rust @rustoftheday