学习资源

Rust 程序设计语言
通过例子学 Rust 中文版
reddit 社区

Rust 统一原则

  • 严格执行数据安全借用
  • 用于对数据进行操作的函数、方法和闭包
  • 用于聚合数据的元组、结构和枚举
  • 用于选择和分解数据的模式匹配
  • 定义数据行为的特征

    注意事项

  • 一切都有作用域

不仅是变量,就连函数和trait内部也可以使用嵌套函数和use。这些无法从外部访问,而且如果不使用就不会出现在代码中。至少我是这样认为的。

  • 必须遵守的命名方案

变量和函数/方法只能使用小写字母、数字和下划线,比如snake_case,但数字不能放在开头。
结构(和其他类型)、枚举(包括枚举值)和trait(但不包括它们的函数/方法)需要以大写字母开头,并且不能包含任何下划线。

  • 没有增量运算符

实际上有,你可以使用i += 1。与赋值相同,该表达式将返回赋值后的值(即,将 i 设置为 i + 1,然后返回 i)。
没有 i++(或者 ++i、i— 和 —i),因为这些运算符有点混乱。

  • 几乎所有的东西都是表达式

除了函数调用之外,还有 if、while、match 和 for 都是表达式。
你可以直接使用 if 来代替其他语言中常见的三元运算符:

  1. let var = if something { 1 } else { 2 };

变量

变量通过 let 声明,并且有作用域。类型是可选的,Rust 非常擅长推断类型(比 Typescript 更出色)。
可以重复声明变量。当重复声明某个变量时,之前声明的变量就会被删除(除非该变量被引用,在这种情况下只有引用会保留,而原始变量会被删除),而且变量的类型也会改变。
在默认情况下,变量是不可变的。如果你想修改它们,则需要在 let 之后加上关键字 mut。

函数

函数的行为几乎与JS一模一样,只不过它们并不是数据类型,而且语法上略有不同。
参数的指定与 Typescript 类似,即key: type。返回类型通过 -> 指定。
有趣的是,虽然 Rust 需要分号,但如果最后一个表达式后面的分号忘写了,它会被作为返回值(即使没有 return 关键字)。

基本类型

数字

● i8、i16、i32、i64、i128:有符号整数,包括负数。数字表示值的比特数。
● u8、u16、u32、u64、u128:无符号整数,从零开始。它们的最大容量翻了一倍,因为有一个额外的比特可用(在有符号整数中用于表示符号)。数字表示值的比特数。
● f32 和 f64:浮点数。javascript 世界中常见的数字。

字符串

● str:简单的UTF-8 字符串(所有 Rust 字符串都是 UTF-8。不能使用无效的 UTF-8 字符串,会引发异常或造成panic)。通常用作指针(即 &str)。
● String:一种更复杂的类型(严格来说不是基本类型),存储在堆中。

数组

● T[] :具有固定长度的数组(如果使用 Option 类型,则数组内包含的元素数量可以小于实际长度)

元组

元组可用于存储不同类型的多个值(从本质上来说就是可以容纳不同类型且大小固定的数组)。
与数组不同,元组可通过点(.)直接访问,例如 tuple.0 表示获取第一项,而 tuples 没有.len() 之类的方法。

常见结构

Option

● 这是一个枚举,值为Some(T) 或 None。(我们稍后再讨论enum,Rust中的枚举与其他语言略有不同。)
● 如果想获取该值,你可以使用 match,就像使用其他枚举一样,或者使用 .unwrap() (如果值为None,则会导致panic)。

Result

● 这个结构与 Option 类似,但常用于处理错误(通常由 IO 方法返回)。
● 它的值是 Ok(T) 或 Err(E)。
● 如果想获取该值,你可以使用match 块或 unwrap()。
● 为了方便使用,当函数返回 Result 时,可以在返回值为 Result(其中E必须为兼容的类型)的方法调用之后使用 ? 来返回错误E(类似于使用.unwrap(),但当函数出错时不会造成panic)。

Vec

● 向量是可增长的数组,存储在堆上。
● 向量支持 .push()、.pop() 等常用操作。详情参见Rust文档。

Box

● 在堆上存储T。可用于在结构中使用enum,或者用于释放栈空间。

结构

结构类似于对象,但它们的大小是静态的。
结构可以通过如下几种方式定义。

使用Tuple

  1. struct Something(u8, u16);

对象表示法

  1. struct Something {
  2. value: u8,
  3. another_value: u16
  4. }

struct作为别名

  1. struct Something = u8;

创建结构实例

类似于C#中定义数组

  1. Something { variable: 1, another_variable: 1234}

enum

  1. enum EnumName {
  2. First,
  3. Second
  4. }

可以为enum指定数值(例如序列化或反序列化数值的情况)

  1. enum EnumName {
  2. First = 1,
  3. Second // auto incremented
  4. }
  1. enum EnumName {
  2. WithValue(u8),
  3. WithMultipleValues(u8, u64, SomeStruct),
  4. CanBeSelf(EnumName),
  5. Empty
  6. }

Match

match是Rust最强大的功能之一。
Match是更强大的switch语句。使用方法与普通的swtich语句一样,除了一点:它必须覆盖所有可能的情况。

一般使用

  1. let var = 1;
  2. match var {
  3. 1 => println!("it's 1"),
  4. 2 => println!("it's 2"),
  5. // 如果不符合以上的条件之一,就默认取_ 的条件
  6. _ => println!("it's not 1 or 2")
  7. }

match范围

  1. match var {
  2. //当变量的范围在1到2之间时,满足该条件
  3. 1..=2 => println("it's between 1 and 2 (both inclusive)"),
  4. //当变量的范围不在1到2之间时,满足该条件
  5. _ => println!("it's something else")
  6. }

什么都不做

  1. match var {
  2. _ => {}
  3. }

match option

可以使用match安全地unwrap Result和Option,以及从其他enum中获取值

  1. let option: Option<u8> = Some(1);
  2. match option {
  3. Some(i) => println!("It contains {i}"),
  4. None => println!("it's empty :c")
  5. //请注意,我们在这里不需要 _,因为 Some 和 None 是 option 的唯一可能值
  6. }

如果你不使用i(或其他值),Rust会发出警告。你可以使用_来代替。

match作为表达式

  1. let option: Option<u8> = Some(1);
  2. let surely = match option {
  3. Some(i) => i,
  4. None => 0
  5. }
  6. println!("{surely}");

你可以使用.unwrap_or(val)来代替上述代码(上述match等价于.unwrap_or(0))。

循环

Loop

  1. loop {
  2. if something { break }
  3. }

该代码会一直运行,直到遇到break(或return,return也会同时返回父函数)

for

  1. for i in 1..3 {} // for(let i = 1; i < 3; i++) // i++ is not a thing, see things to note
  2. for i in 1..=3 {} // for(let i = 1; i <= 3; i++)
  3. for i in 1..=var {} // for(let i = 1; i <= var; i++)
  4. for i in array_or_vec {} // for(let i of array_or_vec) in JS
  5. // again, as most other things, uses a trait, here named "iterator"
  6. // for some types, you need to call `.iter()` or `.into_iter()`.
  7. // Rust Compiler will usually tell you this.
  8. for i in something.iter() {}

while

与其他语言不同,Rust没有do…while,只有最基础的while。

  1. while condition {
  2. looped();
  3. }

打印输出

打印输出可以使用 print! 和 println!。
!表示这是一个宏(即可以扩展成其他代码的快捷方式。
● 输出一行使用 println!()。
● 输出一个静态字符串使用 print!(“something”)。
● 要输出一个实现了Display trait的值(绝大多数基本类型都实现了),可以使用 print!(“{variable}”)。
● 要输出一个实现了Debug trait的值(可以从Display继承),使用 print!(“{variable:?}”)。
● 要输出更复杂的实现了Display trait的内容,使用 print!(“{}”, variable)。
● 要输出更复杂的实现了Debug trait的内容,使用 print!(“{:?}”, variable)。

Trait

Rust没有采用基于继承的系统(面向对象的继承,或JavaScript基于原型的继承),而是采用了鸭子类型(即,如果一个东西像鸭子一样叫,那么它就是鸭子)。
每个类型都有且只有一个“默认”(或匿名)trait,只能在与该类型同一个模块中实现。通常都是该类型独有的方法。

trait定义

  1. trait Duck {
  2. fn quack(&self) -> String;
  3. // 如果鸭子可以跳,则返回
  4. fn can_jump(&self) -> bool { // 默认特征实现
  5. false // 默认情况下,鸭子不能跳跃
  6. }
  7. }
  8. struct Dog(); // 带有空元组的结构
  9. impl Dog { // a nameless default trait.
  10. fn bark(&self) -> String { String::from("bark!") }
  11. }
  12. impl Duck for Dog { // 为 Dog 类型(结构)实现 Duck 特征
  13. fn quack(&self) -> String { String::from("quark!") } // 狗的叫声与鸭子不同
  14. }
  15. let dog = Dog {};
  16. dog.bark();
  17. dog.quack();

首先,我们定义了trait(在面向对象语言中叫做接口,但它只包含方法或函数)。然后为给定的类型(上例中为Dog)实现trait。
一些trait可以自动实现。常见的例子就是Display和Debug trait。这些trait要求,结构中使用的类型必须要相应地实现Display或Debug。

  1. #[derive(Display,Debug)]
  2. struct Something {
  3. var: u8
  4. }
  5. println!("{:?}", Something { var: 1 });

作用域

Trait有作用域,而且与它实现的类型的作用域是独立的。也就是说,你可以使用一个类型,但无法使用一个trait的实现(例如,如果这个实现来自另外一个库,而不是来自该类型本身)。你可以use这个实现。
self
trait中的self指向它实现的类型。&self是指向 self: &Self 的别名,其中Self表示该类型(上例中的 self: &Dog)。self也是self: Self的别名,但两者的区别就是后者会移动变量(即消耗该变量,该变量就无法从外部访问了)。
当函数定义不以self、&self或&mut self开始时(&mut self相当于带有可改变引用的 &self),就是一个静态方法。Trait依然可以像任何方法一样定义并实现静态方法。常见的一个静态方法是new,用于创建类型或结构的实例:

  1. impl Something {
  2. fn new() -> Something {
  3. Something { x: 1 }
  4. }
  5. }
  6. ...
  7. let var = Something::new();

指针

需要保证原始变量在指针的作用域中一直存在。
Rust会在超出作用域时自动释放释放变量。

命名空间

使用全名

使用全名就无需导入。导入只不过是别名。

  1. std::env::args()
  2. use std::env;
  3. env::args()
  4. use std::env::args;
  5. args()

多个“命名空间”选择

选择多个“命名空间”可以使用{}

  1. use std::env::{args, var};

函数内使用use

可以在函数内使用use。这样,如果代码没有被执行,库就不会被导入(即,如果函数没有在代码路径中出现,例如,use了一个测试用的库,而use只写在了测试用例中,那么在正常构建时就不会导入该库)。

  1. fn test() {
  2. use std::env;
  3. env::var();
  4. }

可见性

默认情况下任何东西都是私有的,只能被它所在的文件访问。
要设置为公有(即可以从外部访问),需要使用关键字pub。

使用多个文件

要想“导入”一个文件,要使用 mod指令。
1661588675314.png

  1. pub struct User {
  2. pub active: bool,
  3. pub username: String,
  4. pub email: String,
  5. pub sign_in_count: u64,
  6. }
  7. impl User {
  8. //定义方法
  9. pub fn set_user(username: String) -> User {
  10. User {
  11. active: true,
  12. username: username,
  13. email: String::from("longfuchu@163.com"),
  14. sign_in_count: 22,
  15. }
  16. }
  17. }

使用pub mob重新导出一个文件

  1. pub mod user;
  1. mod models;
  2. fn main() {
  3. create_user();
  4. }
  5. //结构体
  6. fn create_user() {
  7. println!();
  8. let user1 = models::user::User {
  9. email: String::from("someone@example.com"),
  10. username: String::from("someusername123"),
  11. active: true,
  12. sign_in_count: 1,
  13. };
  14. println!("The user1 is '{}' ", user1.email);
  15. }