Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

rust笔记

概述

开始了解rust相关的编程。

rusticon

语言特点

Rust是一种无运行时的强类型语言,它具有许多高级特性,如泛型和lambda等。Rust的内存安全机制比C++更完善,因为它采用了独特的所有权模型。Rust的语法与C++相似,但采用了类型后置的风格,即关键字后跟类型。Rust的变量声明使用let关键字,且变量一旦声明后其类型和值都不能改变,除非使用mut关键字显式地声明变量为可变的。

高性能

Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。

可靠性

Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。

生产力

Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具——包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。

2024-05-23 我现在选择的是 1.78.0 版本。

rust安装

linux/mac机器上

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Rust 的升级非常频繁。如果您安装 Rustup 后已有一段时间,那么很可能您的 Rust 版本已经过时了。运行 rustup update 获取最新版本的 Rust。

vscode里面安装两个插件 rust-analyzerNative Debug

vscode-rust

cargo 教程

Cargo:Rust 的构建工具和包管理器

您在安装 Rustup 时,也会安装 Rust 构建工具和包管理器的最新稳定版,即 Cargo。Cargo 可以做很多事情:

  • cargo build 可以构建项目
  • cargo run 可以运行项目
  • cargo test 可以测试项目
  • cargo doc 可以为项目构建文档
  • cargo publish 可以将库发布到 crates.io。

要检查您是否安装了 Rust 和 Cargo,可以在终端中运行:

1
2
3
4
cargo --version
cargo 1.75.0 (1d8b05cdd 2023-11-20)
# 创建了工程
cargo new greeting

rust输出命令行

1
2
3
4
5
6
fn main() { 
let a = 12;
println!("a is {}", a);
}
// rustc runoob.rs
// ./runoob

Rust基本语法

如果要声明变量,需要使用 let 关键字。例如:

1
let a = 123;

在语言层面尽量少的让变量的值可以改变。所以 a 的值不可变。但这不意味着 a 不是”变量”(英文中的 variable),官方文档称 a 这种变量为”不可变变量”。

1
2
3
4
5
const a: i32 = 123;
let a = 456;

let mut a = 123;
a = 456;

重影(Shadowing)

1
2
3
4
5
6
7
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}
// The value of x is: 12

rust数据类型

整数型(Integer)

位长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

浮点数型(Floating-Point)

1
2
3
4
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}

数学运算

1
2
3
4
5
6
7
fn main() { 
let sum = 5 + 10; // 加
let difference = 95.5 - 4.3; // 减
let product = 4 * 30; // 乘
let quotient = 56.7 / 32.2; // 除
let remainder = 43 % 5; // 求余
}

注意:Rust 不支持 ++ 和 —,因为这两个运算符出现在变量的前后会影响代码可读性,减弱了开发者对变量改变的意识能力。

rust注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
*/

//

文档
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let x = add(1, 2);
///
/// ```

rust函数

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
fn <函数名> ( <参数> ) <函数体>

fn main() {
println!("Hello, world!");
another_function();
}

fn another_function() {
println!("Hello, runoob!");
}
fn main() {
another_function(5, 6);
}

fn another_function(x: i32, y: i32) {
println!("x 的值为 : {}", x);
println!("y 的值为 : {}", y);
}

// 这个语法是错误的
let a = (let b = 2);


fn main() {
let x = 5;

let y = {
let x = 3;
x + 1
};

println!("x 的值为 : {}", x);
println!("y 的值为 : {}", y);
}
// x = 5, y = 4

函数签到
fn main() {
fn five() -> i32 {
5
}
println!("five() 的值为: {}", five());
}

rust条件句子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() { 
let a = 12;
let b;
if a > 0 {
b = 1;
}
else if a < 0 {
b = -1;
}
else {
b = 0;
}
println!("b is {}", b);
}
// 三元条件
fn main() {
let a = 3;
let number = if a > 0 { 1 } else { -1 };
println!("number 为 {}", number);
}

rust循环

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
fn main() {
let mut number = 1;
while number != 4 {
println!("{}", number);
number += 1;
}
println!("EXIT");
}
fn main() {
let a = [10, 20, 30, 40, 50];
for i in a.iter() {
println!("值为 : {}", i);
}
}
fn main() {
let a = [10, 20, 30, 40, 50];
for i in 0..5 {
println!("a[{}] = {}", i, a[i]);
}
}

fn main() {
let s = ['R', 'U', 'N', 'O', 'O', 'B'];
let mut i = 0;
loop {
let ch = s[i];
if ch == 'O' {
break;
}
println!("\'{}\'", ch);
i += 1;
}
}

fn main() {
let s = ['R', 'U', 'N', 'O', 'O', 'B'];
let mut i = 0;
let location = loop {
let ch = s[i];
if ch == 'O' {
break i;
}
i += 1;
};
println!(" \'O\' 的索引为 {}", location);
}

rust迭代器

方法名 描述 示例
next() 返回迭代器中的下一个元素。 let mut iter = (1..5).into_iter(); while let Some(val) = iter.next() { println!(“{}”, val); }
size_hint() 返回迭代器中剩余元素数量的下界和上界。 let iter = (1..10).into_iter(); println!(“{:?}”, iter.size_hint());
count() 计算迭代器中的元素数量。 let count = (1..10).into_iter().count();
nth() 返回迭代器中第 n 个元素。 let third = (0..10).into_iter().nth(2);
last() 返回迭代器中的最后一个元素。 let last = (1..5).into_iter().last();
all() 如果迭代器中的所有元素都满足某个条件,返回 true。 let all_positive = (1..=5).into_iter().all( x x > 0);
any() 如果迭代器中的至少一个元素满足某个条件,返回 true。 let any_negative = (1..5).into_iter().any( x x < 0);
find() 返回迭代器中第一个满足某个条件的元素。 let first_even = (1..10).into_iter().find( x x % 2 == 0);
find_map() 对迭代器中的元素应用一个函数,返回第一个返回 Some 的结果。 let first_letter = “hello”.chars().find_map( c if c.is_alphabetic() { Some(c) } else { None });
map() 对迭代器中的每个元素应用一个函数。 let squares: Vec = (1..5).into_iter().map( x x * x).collect();
filter() 保留迭代器中满足某个条件的元素。 let evens: Vec = (1..10).into_iter().filter( x x % 2 == 0).collect();
filter_map() 对迭代器中的元素应用一个函数,如果函数返回 Some,则保留结果。 let chars: Vec = “hello”.chars().filter_map( c if c.is_alphabetic() { Some(c.to_ascii_uppercase()) } else { None }).collect();
map_while() 对迭代器中的元素应用一个函数,直到函数返回 None。 let first_three = (1..).into_iter().map_while( x if x <= 3 { Some(x) } else { None });
take_while() 从迭代器中取出满足某个条件的元素,直到不满足为止。 let first_five = (1..10).into_iter().take_while( x x <= 5).collect::>()
skip_while() 跳过迭代器中满足某个条件的元素,直到不满足为止。 let odds: Vec = (1..10).into_iter().skip_while( x x % 2 == 0).collect();
for_each() 对迭代器中的每个元素执行某种操作。 let mut counter = 0; (1..5).into_iter().for_each( x counter += x);
fold() 对迭代器中的元素进行折叠,使用一个累加器。 let sum: i32 = (1..5).into_iter().fold(0, acc, x acc + x);
try_fold() 对迭代器中的元素进行折叠,可能在遇到错误时提前返回。 let result: Result = (1..5).into_iter().try_fold(0, acc, x if x == 3 { Err(“Found the number 3”) } else { Ok(acc + x) });
scan() 对迭代器中的元素进行状态化的折叠。 let sum: Vec = (1..5).into_iter().scan(0, acc, x { acc += x; Some(acc) }).collect();
take() 从迭代器中取出最多 n 个元素。 let firstfive = (1..10).into_iter().take(5).collect::<Vec<>>()
skip() 跳过迭代器中的前 n 个元素。 let afterfive = (1..10).into_iter().skip(5).collect::<Vec<>>()
zip() 将两个迭代器中的元素打包成元组。 let zipped = (1..3).zip(&[‘a’, ‘b’, ‘c’]).collect::>()
cycle() 重复迭代器中的元素,直到无穷。 let repeated = (1..3).intoiter().cycle().take(7).collect::<Vec<>>()
chain() 连接多个迭代器。 let combined = (1..3).chain(4..6).collect::>()
rev() 反转迭代器中的元素顺序。 let reversed = (1..4).intoiter().rev().collect::<Vec<>>()
enumerate() 为迭代器中的每个元素添加索引。 let enumerated = (1..4).intoiter().enumerate().collect::<Vec<>>()
peeking_take_while() 取出满足条件的元素,同时保留迭代器的状态,可以继续取出后续元素。 let (first, rest) = (1..10).into_iter().peeking_take_while( &x x < 5);
step_by() 按照指定的步长返回迭代器中的元素。 let evennumbers = (0..10).into_iter().step_by(2).collect::<Vec<>>()
fuse() 创建一个额外的迭代器,它在迭代器耗尽后仍然可以调用 next() 方法。 let mut iter = (1..5).into_iter().fuse(); while iter.next().is_some() {}
inspect() 在取出每个元素时执行一个闭包,但不改变元素。 let mut counter = 0; (1..5).into_iter().inspect( x println!(“Inspecting: {}”, x)).for_each( x println!(“Processing: {}”, x));
same_items() 比较两个迭代器是否产生相同的元素序列。 let equal = (1..5).into_iter().same_items((1..5).into_iter());

在 Rust 中,迭代器(Iterator)是一个允许你逐个访问集合中每个元素的对象。迭代器提供了一种抽象的方法来处理集合,无需显式地管理索引或遍历逻辑。Rust 的迭代器具有惰性,即它们不会在你明确要求时执行任何操作。

迭代器是一个实现了 Iterator trait 的类型,这个 trait 定义了一些方法,其中最重要的是 next 方法,它返回迭代器中的下一个元素。

创建迭代器

Rust 中的集合类型(如数组、向量、哈希表等)都有方法创建迭代器。下面是一些示例:

数组迭代器

1
2
let arr = [1, 2, 3, 4, 5];
let iter = arr.iter(); // 创建一个数组的迭代器

向量迭代器

1
2
let vec = vec![1, 2, 3, 4, 5];
let iter = vec.iter(); // 创建一个向量的迭代器

范围迭代器

1
let iter = 1..6; // 创建一个范围的迭代器,从1到5

使用迭代器

你可以使用 for 循环遍历迭代器:

1
2
3
4
let vec = vec![1, 2, 3, 4, 5];
for val in vec.iter() {
println!("{}", val);
}

常用迭代器适配器

Rust 提供了一些强大的迭代器适配器,用于对迭代器进行变换、过滤和组合。

map

map 方法对每个元素应用一个闭包(函数)并返回一个新的迭代器:

1
2
3
4
5
let vec = vec![1, 2, 3, 4, 5];
let iter = vec.iter().map(|x| x * 2);
for val in iter {
println!("{}", val); // 输出 2, 4, 6, 8, 10
}

filter

filter 方法使用一个闭包对每个元素进行过滤,并返回一个新的迭代器,只保留闭包返回 true 的元素:

1
2
3
4
5
let vec = vec![1, 2, 3, 4, 5];
let iter = vec.iter().filter(|&&x| x % 2 == 0);
for val in iter {
println!("{}", val); // 输出 2, 4
}

collect

collect 方法将迭代器转换为集合类型(如向量、哈希表等):

1
2
3
let vec = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = vec.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // 输出 [2, 4, 6, 8, 10]

fold

fold 方法对迭代器中的每个元素应用一个累加器函数,生成一个单一的值:

1
2
3
let vec = vec![1, 2, 3, 4, 5];
let sum = vec.iter().fold(0, |acc, &x| acc + x);
println!("{}", sum); // 输出 15

自定义迭代器

你也可以实现自己的迭代器,只需要实现 Iterator trait。

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
struct Counter {
count: u32,
}

impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}

impl Iterator for Counter {
type Item = u32;

fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count <= 5 {
Some(self.count)
} else {
None
}
}
}

let mut counter = Counter::new();
while let Some(count) = counter.next() {
println!("{}", count); // 输出 1, 2, 3, 4, 5
}

总结

迭代器是 Rust 中处理集合和序列的强大工具,它们提供了一种灵活、简洁和高效的方式来处理数据。通过使用迭代器适配器,你可以链式调用多个操作,实现复杂的数据变换和过滤。

rust闭包

当然!闭包(Closure)是Rust中的一个强大特性。它允许你捕获周围作用域中的变量,然后在以后调用它们。让我们通过几个简单的例子来解释闭包。

基本语法

闭包的基本语法如下:

1
let closure = |参数| -> 返回类型 { 代码块 };

示例

  1. 一个简单的闭包:
1
2
3
4
let add_one = |x: i32| -> i32 {
x + 1
};
println!("{}", add_one(5)); // 输出: 6

这里,add_one 是一个闭包,它接收一个整数 x,返回 x + 1

  1. 捕获外部变量:

闭包可以自动捕获它们所在环境中的变量:

1
2
3
4
5
let y = 5;
let add_y = |x: i32| -> i32 {
x + y
};
println!("{}", add_y(3)); // 输出: 8

在这个例子中,闭包 add_y 捕获了外部的变量 y

类型推断

Rust 可以自动推断闭包的参数和返回类型,通常不需要显式声明:

1
2
let add_two = |x| x + 2;
println!("{}", add_two(3)); // 输出: 5

闭包的三种类型

根据捕获变量的方式,闭包分为三种类型:

  1. 按引用捕获:使用 &,即借用。
  2. 按值捕获:使用 move,即所有权转移。
  3. 按可变引用捕获:使用 &mut,即可变借用。

示例:

  1. 按引用捕获:
1
2
3
let s = String::from("hello");
let print = || println!("{}", s);
print(); // 输出: hello
  1. 按值捕获:
1
2
3
let s = String::from("hello");
let print = move || println!("{}", s);
print(); // 输出: hello

使用 move 关键字会将变量 s 的所有权移动到闭包内。

  1. 按可变引用捕获:
1
2
3
4
let mut s = String::from("hello");
let mut change = || s.push_str(", world");
change();
println!("{}", s); // 输出: hello, world

在这个例子中,闭包 change 可变地借用了变量 s

结论

闭包是Rust中非常灵活且强大的工具,允许你编写更简洁、更强大的代码。希望这些简单的例子能帮助你理解闭包的基本用法!

rust所有权

在Rust中,所有权(Ownership)是一个核心概念,确保了内存安全性和无数据竞争。让我们通过一些简单的例子和解释来了解所有权。

所有权的基本规则

Rust 的所有权系统有三条基本规则:

  1. 每一个值都有一个所有者(Owner)。
  2. 每一个值同时只能有一个所有者。
  3. 当所有者超出作用域(Scope)时,值会被丢弃(Dropped)。

示例

1. 变量的所有权

1
2
3
4
{
let s = String::from("hello"); // s 拥有这个字符串
// s 可以在这里使用
} // 作用域结束,s 被丢弃

s 超出作用域时,它的内存会被自动释放,这个过程被称为 drop

2. 所有权转移(移动)

1
2
3
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被移动到 s2
// s1 不再有效

在这个例子中,s1 的所有权被移动(Move)给了 s2,所以 s1 不能再被使用。

3. 克隆

如果需要在多个变量中使用同一个值,可以使用 clone 方法:

1
2
3
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 的值被克隆到 s2
println!("{}, {}", s1, s2); // 输出: hello, hello

通过 clone 方法,可以深度复制数据,从而 s1s2 都有效。

引用和借用

Rust 提供了引用(References)来实现借用(Borrowing),这允许你在不转移所有权的情况下使用值。

1. 不可变引用

1
2
3
4
5
6
7
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用 s1
println!("The length of '{}' is {}.", s1, len); // s1 仍然有效

fn calculate_length(s: &String) -> usize {
s.len()
}

使用 & 创建引用,函数 calculate_length 借用了 s1 而不获取其所有权。

2. 可变引用

1
2
3
4
5
6
7
let mut s = String::from("hello");
change(&mut s); // 可变借用 s
println!("{}", s); // 输出: hello, world

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

使用 &mut 创建可变引用,可以修改借用的值。注意:同时只能有一个可变引用,防止数据竞争。

悬垂引用(Dangling References)

Rust 不允许悬垂引用,确保引用永远是有效的。

1
2
3
4
fn dangle() -> &String {
let s = String::from("hello");
&s // 错误:返回对局部变量的引用
} // s 被丢弃

在这个例子中,返回对局部变量的引用会导致悬垂引用,Rust 会在编译时捕获这个错误。

结论

Rust 的所有权系统通过所有权、借用和引用的规则,确保了内存安全性和无数据竞争。这些规则可能需要一些时间来习惯,但它们提供了强大的安全保证。希望这些示例和解释能帮助你理解Rust的所有权概念!

rust切片

在Rust中,slice是一种引用类型,用于表示一个连续的元素序列的子集。它不拥有数据,只是对数据的一种引用。slice可以帮助我们在不复制数据的情况下对数组或向量进行部分操作。以下是一些关于Rust slice的关键概念:

  1. 定义和语法

    • slice是通过一个引用和一个长度来定义的。
    • 语法形式为:&[T]&mut [T],其中T是元素的类型。
    • 不可变slice使用&符号,可变slice使用&mut符号。
  2. 创建和使用

    • 你可以通过引用数组或向量的一部分来创建slice。
    • 例如:let slice = &array[1..4];表示创建一个引用,指向array的第2到第4个元素(不包括第4个元素)。
  3. 特性和功能

    • slice具有与数组类似的功能,例如可以使用索引访问元素。
    • slice的长度是固定的,不能动态改变。
    • slice可以用于函数参数,避免传递整个数组或向量,提升效率。
  4. 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn main() {
    let arr = [1, 2, 3, 4, 5];
    let slice = &arr[1..4]; // 取得数组的第2到第4个元素的slice

    println!("{:?}", slice); // 输出:[2, 3, 4]

    // 使用slice
    for elem in slice.iter() {
    println!("{}", elem);
    }
    }

在这个示例中,slice是对数组arr的第2到第4个元素的引用,通过这种方式,你可以在不复制数据的情况下对这些元素进行操作。

slice在Rust中是一个非常强大的工具,能够让你更高效地处理数据,同时保证内存安全和性能。

这段代码的核心问题在于对String的可变引用和不可变引用之间的冲突。让我们逐行分析代码:

1
2
3
4
5
6
fn main() {
let mut s = String::from("runoob"); // 创建一个可变的String变量s
let slice = &s[0..3]; // 创建一个指向s的前3个字符的不可变slice
s.push_str("yes!"); // 试图向s追加字符串 "yes!",导致错误
println!("slice = {}", slice); // 打印slice
}
  1. 创建String变量mut s

    1
    let mut s = String::from("runoob");

    这里创建了一个可变的String变量s,并初始化为”runoob”。

  2. 创建不可变的slice

    1
    let slice = &s[0..3];

    这里创建了一个不可变的slice,引用s的前3个字符(即”run”)。这个slice是不可变的,并且它引用的是s的部分数据。

  3. 尝试修改String

    1
    s.push_str("yes!");

    这行代码试图向s追加字符串”yes!”。然而,Rust的借用规则规定,在存在一个不可变引用时,不能创建可变引用或修改原数据。这是为了防止数据竞争和保持内存安全。因此,这行代码会导致编译错误。

  4. 打印slice

    1
    println!("slice = {}", slice);

    这行代码会打印slice的内容(即”run”),但由于前一行的错误,程序在编译时就会失败,所以这行代码实际上不会执行。

错误的原因

在Rust中,借用规则如下:

  • 一个变量可以有任意多个不可变引用(&T)。
  • 一个变量在任何时候只能有一个可变引用(&mut T)。
  • 如果存在不可变引用,就不能创建可变引用,反之亦然。

在这段代码中,slice是对s的不可变引用,而随后试图对s进行修改(通过push_str方法),这是不允许的。因此,编译器会报错。

解决方法

为了避免这个错误,可以在修改s之前不持有任何不可变引用。下面是修改后的代码:

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("runoob");
{
let slice = &s[0..3]; // 创建不可变的slice
println!("slice = {}", slice); // 使用slice
} // 这里,slice的作用域结束,不再引用s
s.push_str("yes!"); // 现在可以安全地修改s
println!("s = {}", s); // 打印修改后的s
}

在这个版本中,slice的作用域限制在一个新的块中,当块结束时,slice不再引用s。这样,在后面可以安全地修改s

rust结构体

在Rust中,结构体(struct)是一种自定义数据类型,用于将相关的数据组合在一起。结构体在Rust中有三种主要形式:经典结构体(C-like struct)、元组结构体(tuple struct)和单元结构体(unit-like struct)。下面详细介绍每种结构体并给出相应的示例代码。

1. 经典结构体(C-like Struct)

经典结构体类似于其他编程语言中的结构体或类,具有命名字段。

定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

fn main() {
let user1 = User {
username: String::from("user1"),
email: String::from("user1@example.com"),
sign_in_count: 1,
active: true,
};

println!("Username: {}", user1.username);
println!("Email: {}", user1.email);
println!("Sign in count: {}", user1.sign_in_count);
println!("Active: {}", user1.active);
}

2. 元组结构体(Tuple Struct)

元组结构体是没有命名字段的结构体,使用方式类似于元组。

定义和使用

1
2
3
4
5
6
7
8
9
10
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

println!("Black: ({}, {}, {})", black.0, black.1, black.2);
println!("Origin: ({}, {}, {})", origin.0, origin.1, origin.2);
}

3. 单元结构体(Unit-like Struct)

单元结构体没有任何字段,通常用于实现某种特征或标记。

定义和使用

1
2
3
4
5
struct AlwaysEqual;

fn main() {
let subject = AlwaysEqual;
}

结构体的方法

你可以为结构体定义方法和关联函数,使用impl块来实现。

方法和关联函数示例

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
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
// 关联函数,不需要self参数
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}

// 方法,需要self参数
fn area(&self) -> u32 {
self.width * self.height
}

fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };

println!("The area of the rectangle is {} square pixels.", rect1.area());
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));

let sq = Rectangle::square(20);
println!("The area of the square is {} square pixels.", sq.area());
}

结构体的所有权

结构体中的字段可以拥有数据的所有权,也可以引用其他数据。使用引用时,需要考虑生命周期。

生命周期示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct User<'a> {
username: &'a str,
email: &'a str,
}

fn main() {
let name = String::from("user1");
let email = String::from("user1@example.com");

let user = User {
username: &name,
email: &email,
};

println!("Username: {}", user.username);
println!("Email: {}", user.email);
}

Rust的结构体功能强大且灵活,结合其所有权和借用系统,可以帮助开发者编写安全且高效的代码。

rust枚举类

在Rust中,枚举(enum)是一种定义可能值的集合的数据类型。枚举允许你将一个值与一组预定义的标签(变体)之一关联起来。每个变体可以携带不同类型和数量的数据。枚举在处理有限集合的可能值时特别有用。

定义和使用枚举

基本枚举

这是一个简单的枚举示例,没有附加数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Direction {
Up,
Down,
Left,
Right,
}

fn main() {
let go = Direction::Up;

match go {
Direction::Up => println!("Going up!"),
Direction::Down => println!("Going down!"),
Direction::Left => println!("Going left!"),
Direction::Right => println!("Going right!"),
}
}

带数据的枚举

枚举的每个变体可以携带不同类型和数量的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let messages = [
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write(String::from("Hello")),
Message::ChangeColor(255, 0, 0),
];

for message in &messages {
match message {
Message::Quit => println!("Quit message"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write message: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to (r: {}, g: {}, b: {})", r, g, b),
}
}
}

使用Option枚举

Rust标准库定义了一个非常常用的枚举类型Option,用于处理可能为空的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;

match some_number {
Some(value) => println!("We have a value: {}", value),
None => println!("We have no value"),
}

match absent_number {
Some(value) => println!("We have a value: {}", value),
None => println!("We have no value"),
}
}

使用Result枚举

Rust标准库还定义了一个常用的枚举类型Result,用于处理可能的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn divide(dividend: i32, divisor: i32) -> Result<i32, String> {
if divisor == 0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(dividend / divisor)
}
}

fn main() {
match divide(10, 2) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}

match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}

方法和枚举

你可以为枚举定义方法,类似于为结构体定义方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit message"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write message: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to (r: {}, g: {}, b: {})", r, g, b),
}
}
}

fn main() {
let m = Message::Write(String::from("Hello, world!"));
m.call();
}

总结

Rust中的枚举非常强大,允许定义复杂的类型并对其进行模式匹配。这使得代码更加简洁和易读,同时还能确保在编译时就捕获到许多潜在的错误。

rust组织管理

在Rust中,程序的组织方式主要涉及三个概念:crate、package和module。理解这些概念及其相互关系,有助于编写清晰、可维护的Rust代码。以下是对这三个概念的详细解释:

1. Crate

Crate是Rust中的一个最小的编译单元。每个Rust项目都是一个crate,有两种类型的crate:

  • Binary Crate:生成一个可执行文件。每个binary crate都有一个main函数作为入口点。通常,Rust项目的根目录下有一个src/main.rs文件,它定义了一个binary crate。
  • Library Crate:生成一个库,可以被其他crate使用。通常,Rust项目的根目录下有一个src/lib.rs文件,它定义了一个library crate。

示例:

创建一个binary crate的项目结构:

1
2
3
4
5
my_project

├── Cargo.toml
└── src
└── main.rs

创建一个library crate的项目结构:

1
2
3
4
5
my_library

├── Cargo.toml
└── src
└── lib.rs

2. Package

Package是包含一个或多个crate的文件夹,提供一个Cargo.toml文件来描述这些crate以及它们之间的依赖关系。一个package可以包含以下内容:

  • 至多一个library crate。
  • 零个或多个binary crate。

示例:

一个包含一个library crate和一个binary crate的package结构:

1
2
3
4
5
6
my_package

├── Cargo.toml
└── src
├── lib.rs
└── main.rs

3. Module

Module是用于在crate内部组织代码的工具。通过模块,可以将代码分割成多个逻辑单元,增加代码的可读性和可维护性。模块可以嵌套,并且可以在文件内或跨文件进行定义。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/lib.rs
pub mod network; // 声明一个名为 network 的模块

// src/network.rs
pub fn connect() {
println!("network::connect");
}

// src/main.rs
mod network; // 声明一个名为 network 的模块
use network::connect;

fn main() {
connect();
}

定义和使用模块

你可以在一个文件中定义多个模块,也可以跨多个文件定义模块。

在一个文件中定义模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mod network {
pub fn connect() {
println!("network::connect");
}

mod server {
pub fn start() {
println!("server::start");
}
}
}

fn main() {
network::connect();
network::server::start();
}

跨多个文件定义模块:

假设有以下项目结构:

1
2
3
4
5
6
7
8
9
my_project

├── Cargo.toml
└── src
├── main.rs
├── network
│ ├── mod.rs
│ └── server.rs
└── client.rs

src/main.rs

1
2
3
4
5
6
7
8
mod network;
mod client;

fn main() {
network::connect();
network::server::start();
client::connect();
}

src/network/mod.rs

1
2
3
4
5
pub fn connect() {
println!("network::connect");
}

pub mod server;

src/network/server.rs

1
2
3
pub fn start() {
println!("server::start");
}

src/client.rs

1
2
3
pub fn connect() {
println!("client::connect");
}

总结

  • Crate:最小的编译单元,包含binary crate和library crate。
  • Package:包含一个或多个crate,使用Cargo.toml文件管理。
  • Module:用于在crate内部组织代码,可以嵌套和跨文件。

通过crate、package和module的组合,Rust提供了一种灵活且强大的方式来组织和管理代码,使得代码更易于维护和扩展。
在Rust中,Package 是一个包含一个或多个 crate 的文件夹,并且包含一个 Cargo.toml 文件来管理这些 crate 及其依赖项。可以把 Package 理解为一个独立的工作单元,它提供了一组功能,或者一个应用程序。下面是对 Package 及其相关概念的详细解释:

Package 的核心概念

  1. Cargo.toml 文件:每个 Package 都有一个 Cargo.toml 文件。这个文件包含了 Package 的元数据,比如名称、版本、依赖项等。

  2. Crate:一个 Package 至少包含一个 Crate(可以是 Library Crate 或 Binary Crate),但可以包含多个 Crate。一个 Package 最多包含一个 Library Crate,但可以包含多个 Binary Crate。

  3. src 目录:通常 Package 会有一个 src 目录,其中包含源代码文件。

创建 Package

使用 Cargo 工具来创建一个新的 Package。例如,创建一个名为 my_package 的 Package:

1
cargo new my_package

这个命令会生成以下目录结构:

1
2
3
4
my_package
├── Cargo.toml
└── src
└── main.rs

这个 Package 包含一个 Binary Crate,因为 src 目录下有一个 main.rs 文件。

Cargo.toml 文件

Cargo.toml 文件是 Package 的配置文件,它包含了 Package 的元数据和依赖项信息。以下是一个简单的 Cargo.toml 文件示例:

1
2
3
4
5
6
7
8
[package]
name = "my_package"
version = "0.1.0"
authors = ["Author Name <author@example.com>"]
edition = "2018"

[dependencies]
serde = "1.0"
  • [package]:包含 Package 的基本信息,如名称、版本、作者和编译版本。
  • [dependencies]:列出 Package 所依赖的库。在这个示例中,serde 是一个依赖项。

包含多个 Crate

一个 Package 可以包含一个 Library Crate 和多个 Binary Crate。可以通过创建多个文件来实现。以下是一个包含一个 Library Crate 和两个 Binary Crate 的 Package 示例:

1
2
3
4
5
6
7
my_package
├── Cargo.toml
└── src
├── lib.rs // Library Crate
├── main.rs // Binary Crate 1
└── bin
└── another_binary.rs // Binary Crate 2
  • lib.rs:定义了一个 Library Crate。
  • main.rs:定义了第一个 Binary Crate。
  • bin/another_binary.rs:定义了第二个 Binary Crate。

使用模块组织代码

你可以使用模块在一个 Package 内组织代码。例如:

src/lib.rs

1
2
pub mod network;
pub mod client;

src/network/mod.rs

1
2
3
4
5
pub fn connect() {
println!("network::connect");
}

pub mod server;

src/network/server.rs

1
2
3
pub fn start() {
println!("server::start");
}

src/client.rs

1
2
3
pub fn connect() {
println!("client::connect");
}

总结

  • Package 是一个包含一个或多个 Crate 的独立工作单元,由一个 Cargo.toml 文件管理。
  • 每个 Package 至少包含一个 Crate,可以是 Library Crate 或 Binary Crate。
  • Cargo.toml 文件定义了 Package 的元数据和依赖项。
  • 通过模块(mod)在 Crate 内组织代码,使得代码结构更清晰、更易维护。

这种组织方式使得 Rust 项目可以很容易地扩展和维护,促进代码的重用和模块化。

rust错误处理

Rust 中的错误处理主要通过两种类型实现:ResultOption。其中,Result 用于处理可能失败的操作,而 Option 用于处理可能不存在的值。Rust 的错误处理强调显式处理错误,避免了许多其他编程语言中常见的隐式错误传播问题。

1. 使用 Result 处理错误

Result 枚举用于表示可能的成功或失败结果。它有两个变体:

  • Ok(T):表示操作成功,包含成功的值。
  • Err(E):表示操作失败,包含错误的信息。

示例

假设我们有一个函数用来读取文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::fs::File;
use std::io::{self, Read};

fn read_file_content(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // `?` 运算符自动传播错误
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}

fn main() {
match read_file_content("example.txt") {
Ok(content) => println!("File content: {}", content),
Err(e) => println!("Error reading file: {}", e),
}
}

? 运算符

? 运算符用于简化错误传播。如果函数返回 Result 类型,? 运算符可以在遇到 Err 时返回错误,而在遇到 Ok 时继续执行。

2. 使用 Option 处理值的存在性

Option 枚举用于表示可能存在或不存在的值。它有两个变体:

  • Some(T):表示值存在,包含值。
  • None:表示值不存在。

示例

假设我们有一个函数用来从数组中获取元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn get_element(arr: &[i32], index: usize) -> Option<i32> {
if index < arr.len() {
Some(arr[index])
} else {
None
}
}

fn main() {
let arr = [1, 2, 3, 4, 5];

match get_element(&arr, 2) {
Some(value) => println!("Element at index 2: {}", value),
None => println!("No element at index 2"),
}

match get_element(&arr, 10) {
Some(value) => println!("Element at index 10: {}", value),
None => println!("No element at index 10"),
}
}

3. 自定义错误类型

你可以定义自己的错误类型来处理特定场景下的错误。

示例

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
use std::fmt;

#[derive(Debug)]
enum MyError {
IoError(io::Error),
ParseError,
}

impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::IoError(e) => write!(f, "IO error: {}", e),
MyError::ParseError => write!(f, "Parse error"),
}
}
}

impl From<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::IoError(error)
}
}

fn read_file_and_parse_number(path: &str) -> Result<i32, MyError> {
let content = read_file_content(path)?; // 使用先前定义的 read_file_content 函数
let number: i32 = content.trim().parse().map_err(|_| MyError::ParseError)?;
Ok(number)
}

fn main() {
match read_file_and_parse_number("number.txt") {
Ok(number) => println!("Parsed number: {}", number),
Err(e) => println!("Error: {}", e),
}
}

4. 错误传播

Rust 的错误传播通过 ? 运算符进行,当函数返回类型是 ResultOption 时,? 可以在遇到错误时自动返回。

总结

  • Result:用于处理可能失败的操作,具有 Ok(T)Err(E) 变体。
  • Option:用于处理可能不存在的值,具有 Some(T)None 变体。
  • ? 运算符:用于简化错误传播。
  • 自定义错误类型:可以通过实现 std::fmt::Displaystd::error::Error 特征来自定义错误类型。
  • 错误传播:通过 ? 运算符自动传播错误。

Rust 强调显式错误处理,通过类型系统和模式匹配提供了一种安全且高效的错误处理方式。

rust中的panic

在Rust中,panic! 宏用于处理不可恢复的错误。当程序遇到致命错误时,panic! 宏会终止当前线程的执行,并开始展开(unwind)过程以清理线程中的资源,或直接中止(abort)程序。下面详细介绍 panic! 的机制和处理方式。

1. panic! 的基本用法

panic! 宏通常用于程序遇到无法继续执行的情况,如数组越界、非法状态等。以下是一个简单的示例:

1
2
3
4
fn main() {
let v = vec![1, 2, 3];
println!("{}", v[99]); // 访问越界元素,触发 panic
}

这段代码会因为试图访问不存在的元素而触发 panic!panic! 会打印一个错误信息,并终止程序。

2. 捕获 panic:使用 std::panic::catch_unwind

在某些情况下,你可能希望捕获 panic 而不是让程序崩溃。这可以使用 std::panic::catch_unwind 实现,它允许你捕获 panic 并继续执行。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::panic;

fn main() {
let result = panic::catch_unwind(|| {
println!("About to panic!");
panic!("This is a panic!");
});

match result {
Ok(_) => println!("No panic occurred."),
Err(err) => println!("Caught a panic: {:?}", err),
}

println!("Program continues to run.");
}

在这个示例中,catch_unwind 捕获了 panic,并使程序能够继续运行。

3. 恢复(unwind)与终止(abort)

Rust 在遇到 panic 时有两种处理策略:

  1. 展开(unwind):默认情况下,Rust 会展开栈,清理每一层函数的资源。这种方式允许你在某些情况下捕获并恢复 panic,但会增加一些运行时开销。

  2. 中止(abort):你可以配置Rust在遇到panic时直接终止程序,而不进行栈展开。这种方式更高效,但不允许你捕获和恢复panic

你可以在 Cargo.toml 中配置 panic 策略:

1
2
[profile.release]
panic = 'abort'

4. 自定义 panic 行为:std::panic::set_hook

你可以自定义 panic 时的行为,比如记录日志或打印自定义消息。这可以通过 std::panic::set_hook 实现。

示例

1
2
3
4
5
6
7
8
9
use std::panic;

fn main() {
panic::set_hook(Box::new(|info| {
println!("Custom panic hook: {:?}", info);
}));

panic!("This is a custom panic!");
}

在这个示例中,set_hook 设置了一个自定义的 panic 钩子函数,当 panic 发生时,会执行这个函数。

5. 避免 panic

尽量在代码中避免使用 panic!,特别是在库代码中。可以通过返回 ResultOption 类型来处理可能的错误,从而让调用者决定如何处理错误。

示例:使用 Result 代替 panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}

fn main() {
match divide(4.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(err) => println!("Error: {}", err),
}

match divide(4.0, 0.0) {
Ok(result) => println!("Result: {}", result),
Err(err) => println!("Error: {}", err),
}
}

在这个示例中,divide 函数返回一个 Result 类型,而不是在遇到除零错误时调用 panic!

总结

  • panic!:用于处理不可恢复的错误,会终止当前线程的执行。
  • 捕获 panic:使用 std::panic::catch_unwind 可以捕获 panic 并继续执行。
  • 恢复与终止:Rust 默认使用展开(unwind)策略,你可以配置为中止(abort)策略。
  • 自定义 panic 行为:使用 std::panic::set_hook 可以自定义 panic 时的行为。
  • 避免 panic:尽量使用 ResultOption 处理可能的错误,避免在库代码中使用 panic!

通过这些机制,Rust 提供了灵活且安全的错误处理方式,使得程序更健壮、更易于维护。

rust泛型特征

Rust 中的泛型(Generics)

泛型允许你编写适用于多种数据类型的代码,而无需重复相同的逻辑。它可以用于函数、结构体、枚举和方法。泛型通过类型参数来实现,这些类型参数通常以尖括号包围。

示例:泛型函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let chars = vec!['a', 'b', 'c', 'd'];

println!("Largest number: {}", largest(&numbers));
println!("Largest char: {}", largest(&chars));
}

在这个示例中,largest 函数使用泛型 T 来表示它可以处理任何实现了 PartialOrd 特征的类型。

示例:泛型结构体

1
2
3
4
5
6
7
8
9
10
11
12
struct Point<T> {
x: T,
y: T,
}

fn main() {
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };

println!("Integer Point: ({}, {})", integer_point.x, integer_point.y);
println!("Float Point: ({}, {})", float_point.x, float_point.y);
}

在这个示例中,Point 结构体使用泛型 T 来表示 xy 可以是任何类型。

Rust 中的特征(Traits)

特征定义了一组方法签名,任何实现该特征的类型都必须实现这些方法。特征类似于其他语言中的接口。

示例:定义和实现特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
trait Printable {
fn print(&self);
}

struct Point {
x: i32,
y: i32,
}

impl Printable for Point {
fn print(&self) {
println!("Point({}, {})", self.x, self.y);
}
}

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

在这个示例中,Printable 特征定义了一个 print 方法。Point 结构体实现了这个特征,并提供了 print 方法的具体实现。

示例:带有特征约束的泛型函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
trait Printable {
fn print(&self);
}

fn print_item<T: Printable>(item: T) {
item.print();
}

struct Point {
x: i32,
y: i32,
}

impl Printable for Point {
fn print(&self) {
println!("Point({}, {})", self.x, self.y);
}
}

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

在这个示例中,print_item 函数使用泛型 T 并要求 T 实现 Printable 特征。

总结

  • 泛型:使代码适用于多种数据类型,通过类型参数表示。
    • 用法:函数、结构体、枚举和方法。
  • 特征:定义共享行为,类似于接口。
    • 用法:定义方法签名,并由具体类型实现。

通过这两个概念,Rust 提供了强大的抽象能力,使得代码更加灵活和可重用。

rust生命周期

在 Rust 中,生命周期(lifetime)是一个重要的概念,用于管理引用的有效范围,确保引用在使用时是有效的。Rust 的生命周期系统是其内存安全的重要保证之一,防止悬垂引用和数据竞争问题。下面是对生命周期的简要说明,以及它与其他语言的不同之处。

Rust 中的生命周期

  1. 基本概念

    • 生命周期用来描述引用的有效范围。
    • Rust 编译器通过生命周期来检查引用的有效性,确保引用在使用时是有效的。
  2. 显式生命周期标注

    • 当函数涉及多个引用参数时,可能需要显式标注生命周期。
    • 生命周期参数以单引号开头,如 'a,并放在引用类型的前面。

示例:简单的生命周期标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

fn main() {
let string1 = String::from("long string");
let string2 = "short";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}

在这个例子中,'a 是生命周期参数,表示 xy 和返回值的生命周期是相同的,即它们都必须在调用者的上下文中有效。

与其他语言的不同之处

  1. 编译时检查

    • Rust 的生命周期是在编译时检查的,而不像其他一些语言(如 C/C++)在运行时才会出现悬垂指针问题。
    • 通过编译时检查,Rust 保证了内存安全,避免了悬垂引用和数据竞争。
  2. 显式生命周期注解

    • 在 Rust 中,有时需要显式地标注生命周期,特别是在函数签名中。这在其他语言中通常是隐式处理的。
    • 这种显式性增强了代码的可读性和可维护性,明确了引用的关系和作用域。
  3. 借用检查器

    • Rust 的借用检查器(borrow checker)会在编译时确保所有引用在其生命周期内有效,防止数据竞争和悬垂指针。
    • 这与手动管理内存的语言(如 C/C++)形成鲜明对比,在这些语言中,程序员需要自行保证引用的有效性。

生命周期消除

在许多情况下,Rust 编译器可以通过生命周期消除(lifetime elision)推断生命周期参数,减少显式标注的需要。例如:

1
2
3
4
fn first_word(s: &str) -> &str {
// 编译器会推断出这个函数的生命周期标注
&s[..1]
}

总结

  • 生命周期:用于管理引用的有效范围,防止悬垂引用和数据竞争。
  • 显式标注:需要在函数签名中显式标注生命周期参数,以描述引用之间的关系。
  • 编译时检查:Rust 的生命周期检查在编译时进行,确保内存安全。
  • 与其他语言不同:Rust 的显式生命周期和编译时检查机制,与其他手动管理内存的语言形成对比,提高了内存安全性和代码可读性。

通过生命周期系统,Rust 在保证内存安全的同时,保持了高效的性能和灵活性。

面向对象

在 Rust 中,虽然没有像 C++ 或 Java 那样直接的面向对象(OO)语法,但它通过结构体、特征(traits)和方法实现了面向对象的核心概念:封装、继承和多态。

封装(Encapsulation)

封装是通过定义结构体(struct)和实现方法(impl)来实现的。

示例:封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
// 关联函数,类似于静态方法
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}

// 方法,具有不可变借用 &self
fn area(&self) -> u32 {
self.width * self.height
}
}

fn main() {
let rect = Rectangle::new(30, 50);
println!("The area of the rectangle is {} square pixels.", rect.area());
}

继承(Inheritance)

Rust 没有传统的继承,但可以通过特征(traits)实现类似的行为。通过特征定义共享的行为,并在结构体中实现这些特征。

示例:继承(通过特征)

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
trait Shape {
fn area(&self) -> f64;
}

struct Circle {
radius: f64,
}

impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

struct Square {
side: f64,
}

impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}

fn main() {
let circle = Circle { radius: 5.0 };
let square = Square { side: 4.0 };

println!("Circle area: {}", circle.area());
println!("Square area: {}", square.area());
}

多态(Polymorphism)

多态通过特征对象(trait objects)和动态分发(dynamic dispatch)实现。可以在运行时通过特征对象调用不同类型的实现。

示例:多态

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
trait Shape {
fn area(&self) -> f64;
}

struct Circle {
radius: f64,
}

impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

struct Square {
side: f64,
}

impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}

fn print_area(shape: &dyn Shape) {
println!("The area is {}", shape.area());
}

fn main() {
let circle = Circle { radius: 5.0 };
let square = Square { side: 4.0 };

print_area(&circle);
print_area(&square);
}

在这个示例中,print_area 函数接受一个实现了 Shape 特征的引用。无论传递的是 Circle 还是 Square,都会调用对应的 area 方法,这就是多态的实现。

总结

  • 封装:使用结构体(struct)和方法(impl)实现,将数据和行为封装在一起。
  • 继承:通过特征(traits)实现类似继承的行为,共享方法定义。
  • 多态:使用特征对象(trait objects)和动态分发(dynamic dispatch)实现多态。

通过这些机制,Rust 提供了强大的抽象能力,同时保持了内存安全和高性能。

rust并发编程

  • 并发:在同一时间段内处理多个任务,通过任务切换交替执行。(Concurrency)
  • 并行:在同一时刻同时执行多个任务,通过多个处理单元同时执行。(Parallelism)
    Rust 的并发编程提供了几种方式来实现线程和任务并行执行,重点在于安全性和效率。以下是一些最常用的方法,用最简单的方式来说明。

1. 使用 std::thread

Rust 的标准库提供了线程支持,可以使用 std::thread 创建和管理线程。

示例:创建一个简单的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::thread;
use std::time::Duration;

fn main() {
// 创建一个线程
let handle = thread::spawn(|| {
for i in 1..10 {
println!("Hello from the spawned thread! Number {}", i);
thread::sleep(Duration::from_millis(1));
}
});

// 主线程的工作
for i in 1..5 {
println!("Hello from the main thread! Number {}", i);
thread::sleep(Duration::from_millis(1));
}

// 等待创建的线程完成
handle.join().unwrap();
}

在这个示例中,主线程和新创建的线程同时执行各自的任务。

2. 使用 std::sync::mpsc 进行线程间通信

Rust 提供了 mpsc(multiple producer, single consumer)通道,用于在线程之间传递消息。

示例:在线程之间传递消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
let (tx, rx) = mpsc::channel();

// 创建一个线程,并发送消息
thread::spawn(move || {
let val = String::from("Hello from the thread");
tx.send(val).unwrap();
});

// 接收消息
let received = rx.recv().unwrap();
println!("Got: {}", received);
}

3. 使用 std::sync::Mutex 进行共享状态的并发访问

Rust 提供了 Mutex 来确保只有一个线程可以访问共享数据。

示例:使用 Mutex 进行共享数据访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());
}

在这个示例中,Arc(原子引用计数)用于在多个线程之间共享 MutexMutex 确保每次只有一个线程能够修改 counter

4. 使用 asyncawait 进行异步编程

Rust 的异步编程通过 asyncawait 关键字来实现,通常与异步运行时库(如 Tokio)一起使用。

示例:异步函数和任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
let task1 = async {
sleep(Duration::from_secs(1)).await;
println!("Task 1 completed");
};

let task2 = async {
sleep(Duration::from_secs(2)).await;
println!("Task 2 completed");
};

tokio::join!(task1, task2);
println!("All tasks completed");
}

在这个示例中,两个异步任务 task1task2 被并发执行,并使用 tokio::join! 来等待它们完成。

总结

  • std::thread:用于创建和管理线程。
  • std::sync::mpsc:用于线程间的消息传递。
  • std::sync::Mutex:用于共享状态的安全访问。
  • asyncawait:用于异步编程,通常与异步运行时库(如 Tokio)一起使用。

Rust 的并发编程通过这些工具和概念,提供了安全高效的并发执行方式。
好的,在 Rust 中,除了 std::sync::mpsc 提供的通道(channel),还有其他库提供的通道,比如 crossbeam,它提供了更强大和灵活的并发工具。下面我们结合 std::sync::mpsccrossbeam,重新讲解 Rust 中的并发编程。

1. 使用 std::thread

Rust 的标准库提供了线程支持,可以使用 std::thread 创建和管理线程。

示例:创建一个简单的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::thread;
use std::time::Duration;

fn main() {
// 创建一个线程
let handle = thread::spawn(|| {
for i in 1..10 {
println!("Hello from the spawned thread! Number {}", i);
thread::sleep(Duration::from_millis(1));
}
});

// 主线程的工作
for i in 1..5 {
println!("Hello from the main thread! Number {}", i);
thread::sleep(Duration::from_millis(1));
}

// 等待创建的线程完成
handle.join().unwrap();
}

2. 使用 std::sync::mpsc 进行线程间通信

Rust 提供了 mpsc(multiple producer, single consumer)通道,用于在线程之间传递消息。

示例:在线程之间传递消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
let (tx, rx) = mpsc::channel();

// 创建一个线程,并发送消息
thread::spawn(move || {
let val = String::from("Hello from the thread");
tx.send(val).unwrap();
});

// 接收消息
let received = rx.recv().unwrap();
println!("Got: {}", received);
}

3. 使用 std::sync::Mutex 进行共享状态的并发访问

Rust 提供了 Mutex 来确保只有一个线程可以访问共享数据。

示例:使用 Mutex 进行共享数据访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());
}

在这个示例中,Arc(原子引用计数)用于在多个线程之间共享 MutexMutex 确保每次只有一个线程能够修改 counter

4. 使用 crossbeam 的通道

crossbeam 是一个更强大和灵活的并发库,提供了多种并发原语,包括通道。

示例:使用 crossbeam 的通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use crossbeam::channel;
use std::thread;

fn main() {
let (s, r) = channel::unbounded();

for i in 0..10 {
let s = s.clone();
thread::spawn(move || {
s.send(i).unwrap();
println!("Sent: {}", i);
});
}

for _ in 0..10 {
let received = r.recv().unwrap();
println!("Received: {}", received);
}
}

在这个示例中,crossbeam::channel::unbounded 创建了一个无界通道,允许任意多的消息发送和接收。

5. 使用 asyncawait 进行异步编程

Rust 的异步编程通过 asyncawait 关键字来实现,通常与异步运行时库(如 Tokio)一起使用。

示例:异步函数和任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
let task1 = async {
sleep(Duration::from_secs(1)).await;
println!("Task 1 completed");
};

let task2 = async {
sleep(Duration::from_secs(2)).await;
println!("Task 2 completed");
};

tokio::join!(task1, task2);
println!("All tasks completed");
}

在这个示例中,两个异步任务 task1task2 被并发执行,并使用 tokio::join! 来等待它们完成。

总结

  • std::thread:用于创建和管理线程。
  • std::sync::mpsc:用于线程间的消息传递,支持多个生产者单个消费者。
  • std::sync::Mutex:用于共享状态的安全访问。
  • crossbeam::channel:提供了更灵活的通道,支持多种并发模式。
  • asyncawait:用于异步编程,通常与异步运行时库(如 Tokio)一起使用。

Rust 的并发编程通过这些工具和概念,提供了安全高效的并发执行方式,适用于不同的并发场景。

Rust 智能指针

在 Rust 中,智能指针(Smart Pointers)是一种具有指针行为的数据结构,不仅能够指向某个值,还包含额外的元数据和功能。Rust 的智能指针提供了更高的安全性和便捷性,通过所有权和借用规则来管理内存,避免了手动管理内存的常见问题。以下是 Rust 中常用的几种智能指针及其用途。

1. Box<T>

Box<T> 是最简单的智能指针,用于在堆上分配值。它提供了对数据的所有权,但不会引入额外的开销。

示例

1
2
3
4
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}

在这个示例中,Box::new 在堆上分配了一个值 5,并返回一个指向该值的 Box<T>

2. Rc<T>

Rc<T> 是一个引用计数智能指针,用于在多个所有者之间共享数据。它允许多个变量持有同一个值的所有权,但只能用于单线程环境。

示例

1
2
3
4
5
6
7
8
9
10
use std::rc::Rc;

fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
let c = Rc::clone(&a);

println!("a = {}, b = {}, c = {}", a, b, c);
println!("Reference count: {}", Rc::strong_count(&a));
}

在这个示例中,Rc::clone 创建了指向同一值的多个引用,并增加了引用计数。

3. Arc<T>

Arc<T> 是一个原子引用计数智能指针,类似于 Rc<T>,但可以在线程之间安全地共享数据。它用于需要跨线程共享所有权的场景。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::sync::Arc;
use std::thread;

fn main() {
let a = Arc::new(5);
let b = Arc::clone(&a);

let handle = thread::spawn(move || {
println!("b = {}", b);
});

handle.join().unwrap();
println!("a = {}", a);
}

在这个示例中,Arc::clone 创建了跨线程共享的引用。

4. RefCell<T>

RefCell<T> 允许在运行时检查借用规则,而不是在编译时。它提供了内部可变性,即使在不可变环境中也可以修改数据。RefCell 仅用于单线程环境。

示例

1
2
3
4
5
6
7
8
9
use std::cell::RefCell;

fn main() {
let x = RefCell::new(5);

*x.borrow_mut() += 1;

println!("x = {}", x.borrow());
}

在这个示例中,RefCell::borrow_mutRefCell::borrow 用于可变和不可变借用,分别在运行时检查借用规则。

5. Mutex<T>

Mutex<T> 提供互斥锁机制,用于在线程之间安全地共享数据。它确保一次只有一个线程可以访问数据。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());
}

在这个示例中,Mutex::lock 获取互斥锁,确保一次只有一个线程可以访问和修改 counter

总结

  • Box<T>:用于在堆上分配值,提供简单的所有权。
  • Rc<T>:用于单线程环境下的引用计数共享所有权。
  • Arc<T>:用于多线程环境下的原子引用计数共享所有权。
  • RefCell<T>:提供运行时的内部可变性检查,适用于单线程环境。
  • Mutex<T>:提供互斥锁机制,确保线程安全的共享访问。

Rust 的智能指针通过所有权、借用和引用计数等机制,提供了安全高效的内存管理,避免了手动管理内存的常见问题。

rust异步编程

Rust 的异步编程(asynchronous programming)提供了一种处理并发任务的方式,避免了传统多线程编程中的复杂性和开销。通过使用 asyncawait 关键字,Rust 能够以异步的方式执行代码,这对于 IO 密集型任务非常有效,比如网络请求、文件操作等。

基本概念

  1. async/await:Rust 使用 async 关键字定义异步函数,使用 await 关键字等待异步操作完成。
  2. Future:异步函数返回一个 Future,它代表一个在某个时间点将要完成的值。
  3. Executor:执行异步任务的运行时环境,常用的有 Tokioasync-std

async 和 await

使用 asyncawait 关键字可以让你以同步的方式编写异步代码。

示例:基本的 async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
let task1 = async {
sleep(Duration::from_secs(1)).await;
println!("Task 1 completed");
};

let task2 = async {
sleep(Duration::from_secs(2)).await;
println!("Task 2 completed");
};

tokio::join!(task1, task2);
println!("All tasks completed");
}

在这个示例中,两个异步任务 task1task2 并发执行,并使用 tokio::join! 等待它们完成。

使用 Tokio 运行时

Tokio 是 Rust 中常用的异步运行时,它提供了执行异步任务的环境。

示例:使用 Tokio 进行异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use tokio::time::{sleep, Duration};

async fn perform_task(name: &str, duration: u64) {
sleep(Duration::from_secs(duration)).await;
println!("{} completed", name);
}

#[tokio::main]
async fn main() {
let task1 = perform_task("Task 1", 1);
let task2 = perform_task("Task 2", 2);

tokio::join!(task1, task2);
println!("All tasks completed");
}

在这个示例中,perform_task 是一个异步函数,接受任务名称和持续时间。main 函数使用 tokio::join! 并发执行两个任务。

异步函数和 Future

异步函数返回一个 Future,表示一个尚未完成的计算。你可以手动创建和操作 Future

示例:手动创建 Future

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
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct SimpleFuture {
completed: bool,
}

impl Future for SimpleFuture {
type Output = &'static str;

fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.completed {
Poll::Ready("Future completed")
} else {
self.completed = true;
Poll::Pending
}
}
}

#[tokio::main]
async fn main() {
let my_future = SimpleFuture { completed: false };
let result = my_future.await;
println!("{}", result);
}

在这个示例中,SimpleFuture 是一个自定义的 Future,它在第一次调用 poll 时返回 Poll::Pending,在第二次调用时返回 Poll::Ready

异步编程的优势

  • 高效 IO:异步编程可以高效地处理 IO 操作,不需要为每个任务创建一个线程,从而节省资源。
  • 提高响应性:通过异步编程,可以在等待某个任务完成时执行其他任务,提高程序的响应性。

使用 async-std

除了 Tokio,还有另一个常用的异步运行时库 async-std

示例:使用 async-std

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use async_std::task;
use async_std::prelude::*;

async fn perform_task(name: &str, duration: u64) {
task::sleep(Duration::from_secs(duration)).await;
println!("{} completed", name);
}

fn main() {
task::block_on(async {
let task1 = perform_task("Task 1", 1);
let task2 = perform_task("Task 2", 2);

futures::join!(task1, task2);
});

println!("All tasks completed");
}

在这个示例中,使用 async-std 运行时来执行异步任务。

总结

  • async/await:Rust 提供的关键字,用于定义和执行异步函数。
  • Future:表示一个尚未完成的计算,异步函数返回 Future
  • Executor:执行异步任务的运行时环境,如 Tokioasync-std
  • 高效 IO 和响应性:异步编程能够高效处理 IO 操作并提高程序的响应性。

通过以上示例和概念,您可以开始在 Rust 中编写和执行异步代码,提高程序的性能和效率。

引用