rust开发
为什么要学习Rust?
Rust 语言的特点
- 内存安全:Rust 的所有权系统在编译时防止空悬指针、数据竞争等内存错误,无需垃圾收集器。
- 并发编程:Rust 提供了现代的语言特性来支持并发编程,如线程和消息传递,使得编写并发程序更加安全和容易。
- 性能:Rust 编译为机器码,没有运行时或垃圾收集器,能够提供接近 C 和 C++ 的性能。
- 类型系统:Rust 的类型系统和模式匹配提供了强大的抽象能力,有助于编写更安全、更可预测的代码。
- 错误处理:Rust 的错误处理模型鼓励显式处理所有可能的错误情况。
- 宏系统:Rust 提供了一个强大的宏系统,允许开发者在编译时编写和重用代码。
- 包管理:Rust 的包管理器 Cargo 简化了依赖管理和构建过程。
- 跨平台:Rust 支持多种操作系统和平台,包括 Windows、macOS、Linux、BSDs 等。
- 社区支持:Rust 有一个活跃的社区,提供了大量的库和工具。
- 工具链:Rust 拥有丰富的工具链,包括编译器、包管理器、文档生成器等。
- 无段错误:Rust 的所有权和生命周期规则保证了引用的有效性,从而避免了段错误。
- 迭代器和闭包:Rust 提供了强大的迭代器和闭包支持,简化了集合的处理。
Rust 基础语法
输出
rust有两个标准输出的函数,println!()
和print!()
,前者和后者的区别仅在于前者会多输出一个换行符
占位符
C语言中使用%(字母)
作为占位符,rust中使用的是{}
进行占位
例如:
1 | fn main() { |
输出得到:a is 10
如果将a输出两遍,不需要在最后写两遍a,此时可以将后面的参数看成是一个数组,然后可以通过索引访问
例如:
1 | fn main() { |
输出得到:a is 10, a again is 10
转义字符
rust的转义字符除{}
外都是前面加反斜杠(和C语言一样),{}
的转义是{{`和`}}
例如:
1 | fn main() { |
输出:{}
变量
声明
rust使用let声明变量,但是一般声明的变量是不可变的变量
1 | let a = 10; |
如果需要后面对a
再进行赋值,需要添加mut
关键字
1 | let mut a = 10; |
使用mut
关键字声明的叫可变变量,没有使用mut
关键字声明的是不可变变量
不可变变量与常量
不可变变量是不能重新进行赋值,但是可以重新进行声明的变量,常量不能重新赋值,也不能重新声明
1 | let a = 10; |
1 | const a = 10; |
数据类型
rust是静态语言类型,在声明变量的时候可以显式指定类型
基本类型:
i32 | 32位有符号整数 |
---|---|
u32 | 32位无符号整数 |
f64 | 64位浮点数 |
bool | 布尔类型 |
char | 字符 |
如果不显式指定,那么编译器会自动判断该变量的类型
整数型:
位长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
isize 和 usize 两种整数类型是用来衡量数据大小的,它们的位长度取决于所运行的目标平台,如果是 32 位架构的处理器将使用 32 位位长度整型。
整数的表述方法有以下几种:
进制 | 例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节(只能表示 u8 型) | b’A’ |
在上面的例子中,可以看到数字中间添加了下划线,下划线本身没有任何意义,只是提高了代码的可读性,1_234
和1234
是一样的,前者更容易读出他是一个“千”位的数字
浮点数型(Floating-Point)
默认情况下,64.0 将表示 64 位浮点数,因为现代计算机处理器对两种浮点数计算的速度几乎相同,但 64 位浮点数精度更高。
字符型
字符型用 char 表示。
Rust的 char 类型大小为 4 个字节,代表 Unicode标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至表情符号和零宽度空格在 Rust 中都是有效的 char 值。
Unicode 值的范围从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF (包括两端)。 但是,”字符”这个概念并不存在于 Unicode 中,因此您对”字符”是什么的直觉可能与Rust中的字符概念不匹配。所以一般推荐使用字符串储存 UTF-8 文字(非英文字符尽可能地出现在字符串中)。
注意:由于中文文字编码有两种(GBK 和 UTF-8),所以编程中使用中文字符串有可能导致乱码的出现,这是因为源程序与命令行的文字编码不一致,所以在 Rust 中字符串和字符都必须使用 UTF-8 编码,否则编译器会报错。
运算
rust支持自运算操作,如sum += 1
= sum = sum + 1
但是不支持自增和自减的++
和--
复合类型
元组是用一对 ()
包括的一组数据,可以包含不同种类的数据:
1 | let tup: (i32, f64, u8) = (500, 6.4, 1); |
数组用一对 []
包括的同类型数据。
1 | let a = [1, 2, 3, 4, 5]; |
函数
函数的基本格式:
1 | fn <函数名> ( <参数> ) <函数体> |
rust函数命名风格是小写字母加下划线分割
rust语言函数定义位置没有要求,可以在任意位置定义函数
定义函数如果有参数,则必须声明参数名称和类型:
1 | fn main() { |
Rust 中可以在一个用 {}
包括的块里编写一个较为复杂的表达式:
1 | fn main() { |
结果:
1 | x 的值为 : 5 |
在上面的程序中,包含了一个表达式块:
1 | { |
而且在块中可以使用函数语句,最后一个步骤是表达式,此表达式的结果值是整个表达式块所代表的值。这种表达式块叫做函数体表达式。
注意:**x + 1
**之后没有分号,否则它将变成一条语句
rust不支持自动判断返回值类型,需要在函数声明时定义返回值的类型
1 | fn add(a: i32, b: i32) -> i32 { |
如果没有明确声明函数返回值的类型,函数将被认为是”纯过程”,不允许产生返回值,return 后面不能有返回值表达式。
而函数体表达式和函数体不一样,函数体表达式不能使用return关键字。
条件语句
基础语法
1 | fn main() { |
Rust 中的条件表达式必须是 bool 类型,例如下面的程序是错误的:
1 | fn main() { |
虽然 C/C++ 语言中的条件表达式用整数表示,非 0 即真,但这个规则在很多注重代码安全性的语言中是被禁止的。
所以可以使用函数体表达式构造一个三元条件运算
1 | fn main() { |
结果:
1 | number 为 1 |
循环
while循环
1 | fn main() { |
for循环
for循环感觉和Python的for很像
以下例子中a.iter() 代表 a 的迭代器(iterator)
1 | fn main() { |
1 | fn main() { |
loop循环
这是rust原生的无限循环结构
1 | fn main() { |
迭代器
创建迭代器
最常见的方式是通过集合的 .iter()
、.iter_mut()
或 .into_iter()
方法来创建迭代器:
.iter()
:返回集合的不可变引用迭代器。.iter_mut()
:返回集合的可变引用迭代器。.into_iter()
:将集合转移所有权并生成值迭代器。
.iter()
方法返回一个不可变引用的迭代器。可以遍历数组中的元素,但不能修改它们。
1 | for element in arr.iter() { |
.iter_mut()
方法返回一个可变引用的迭代器。不仅可以遍历数组中的元素,还可以修改它们的值。
1 | for element in arr.iter_mut() { |
这里,element
是数组元素的可变引用,因此可以通过解引用运算符 *
来修改元素的值。
解引用运算符
*
在 Rust 中用于访问引用指向的值。引用在 Rust 中是一种指针类型,它允许你引用内存中的某个值而不需要拥有该值。解引用运算符*
可以让你获取引用指向的实际值。
1
2
3
4
5
6
7 fn main() {
let a = 10;
let b = &a; // b 是 a 的引用
println!("a 的值是 {}", a);
println!("b 引用的值是 {}", *b); // 使用 *b 来获取 b 引用的值
}
.into_iter()
方法将数组的所有权转移给迭代器,并生成一个值迭代器。这意味着迭代器会直接生成数组中的元素,而不是它们的引用。在遍历过程中,数组的所有权会被移动到迭代器中。
1 | for element in arr.into_iter() { |
这里,element
是数组中的元素本身。需要注意的是,使用 .into_iter()
之后,arr
就不再可用,因为它已经将所有权转移给了迭代器。
迭代器方法
Rust 的迭代器提供了丰富的方法来处理集合中的元素,其中一些常见的方法包括:
map()
:对每个元素应用给定的转换函数。
map()
方法对迭代器中的每个元素应用一个指定的函数,返回一个新的迭代器,其中包含应用该函数后的结果。
1 | let arr = [1, 2, 3, 4]; |
在这个例子中,map()
方法将数组中的每个元素乘以 2,返回一个新的迭代器,这个迭代器会被收集到一个向量中。
filter()
:根据给定的条件过滤集合中的元素。
1 | let arr = [1, 2, 3, 4]; |
在这个例子中,filter()
方法只保留了数组中能被 2 整除的元素。
fold()
:对集合中的元素进行累积处理。
1 | let arr = [1, 2, 3, 4]; |
在这个例子中,fold()
方法从初始值 0 开始,对数组中的每个元素进行累加,最终得到数组元素的总和。
skip()
:跳过指定数量的元素。
1 | let arr = [1, 2, 3, 4]; |
在这个例子中,skip(2)
方法跳过了数组中的前两个元素,返回了从第三个元素开始的新迭代器。
take()
:获取指定数量的元素。
1 | let arr = [1, 2, 3, 4]; |
在这个例子中,take(2)
方法只获取了数组中的前两个元素。
enumerate()
:为每个元素提供索引。
1 | let arr = [10, 20, 30, 40]; |
在这个例子中,enumerate()
方法为数组中的每个元素提供了一个索引,你可以同时访问索引和元素的值。
迭代器内容好多啊。。。不想学了,用的时候再学,更多内容请看Rust 迭代器 | 菜鸟教程 (runoob.com)
闭包
一开始学没太明白这是个什么东西,他和函数差不多,所以先看一下它和函数的区别:
特性 | 闭包 | 函数 |
---|---|---|
匿名性 | 是匿名的,可存储为变量 | 有固定名称 |
环境捕获 | 可以捕获外部变量 | 不能捕获外部变量 |
定义方式 | ` | 参数 |
类型推导 | 参数和返回值类型可以推导 | 必须显式指定 |
存储与传递 | 可以作为变量、参数、返回值 | 同样支持 |
闭包的声明
闭包的语法声明:
1 | let closure_name = |参数列表| 表达式或语句块; |
参数可以有类型注解,也可以省略,Rust 编译器会根据上下文推断它们。
1 | let add_one = |x: i32| x + 1; |
闭包的参数和返回值: 闭包可以有零个或多个参数,并且可以返回一个值。
1 | let calculate = |a, b, c| a * b + c; |
闭包的调用:闭包可以像函数一样被调用。
1 | let result = calculate(1, 2, 3); |
匿名函数
闭包在 Rust 中类似于匿名函数,可以在代码中以 {} 语法块的形式定义,使用 || 符号来表示参数列表,实例如下:
1 | let add = |a, b| a + b; |
在这个示例中,add 是一个闭包,接受两个参数 a 和 b,返回它们的和。
捕获外部变量
闭包可以捕获周围环境中的变量,这意味着它可以访问定义闭包时所在作用域中的变量。例如:
1 | let x = 5; |
以上代码中,闭包 square 捕获了外部变量 x,并在闭包体中使用了它。
闭包可以通过三种方式捕获外部变量:
- 按引用捕获(默认行为,类似
&T
) - 按值捕获(类似
T
) - 可变借用捕获(类似
&mut T
)
1 | fn main() { |
说明:
- 闭包默认按引用捕获外部变量。
- 使用
move
关键字可以强制按值捕获,将外部变量的所有权转移到闭包内。 - 如果闭包需要修改外部变量,需显式声明为
mut
闭包。
移动与借用
闭包可以通过 move 关键字获取外部变量的所有权,或者通过借用的方式获取外部变量的引用。例如:
借用变量:默认情况下,闭包会借用它捕获的环境中的变量,这意味着闭包可以使用这些变量,但不能改变它们的所有权。这种情况下,闭包和外部作用域都可以使用这些变量。例如:
1 | let x = 10; |
获取所有权:通过在闭包前添加 move 关键字,闭包会获取它捕获的环境变量的所有权。这意味着这些变量的所有权会从外部作用域转移到闭包内部,外部作用域将无法再使用这些变量。例如:
1 | let s = String::from("hello"); |
通过这两种方式,Rust 提供了灵活的机制来处理闭包与外部变量之间的关系,使得在编写并发、安全的代码时更加方便。
闭包也就先学到这里,更多请看Rust 闭包 | 菜鸟教程 (runoob.com)
所有权
所有权规则:
- Rust 中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 当所有者不在程序运行范围时,该值将被删除。
变量范围
1 | { |
变量范围是变量的一个属性,其代表变量的可行域,默认从声明变量开始有效直到变量所在域结束。
内存和分配
如在C语言中,假设有一个字符串Hello World
,然后有以下程序
1 | { |
在C语言程序中需要使用free
手动释放内存,但是在rust中,不需要手动释放内存,当一个变量在其变量范围结束时rust会自动释放该变量。