Rust基础

无代码,只作知识点记住,详细可查官方wiki:doc

包管理器

包管理器的详细操作这里就不写了,网上都可以找到。

使用cargo时,可以安装一个插件: cargo-edit。安装好后就可以使用cargo add [包名字]来安装库,可以使用cargo rm [包名字]来删除库。

变量与不可变性

变量的基础知识

  1. 可以使用let关键字来声明变量
  2. Rust支持类型推导,但可以显式指定变量的类型:let x: i32 = 5;
  3. 变量名采用蛇形命名法,而枚举与结构体使用帕期卡命名法
    1. 如果一个变量没有使用到,可以使用前置下划线来消除警告
  4. 使用as实现强行类型转换:let a = 3.1; let b = a as i32;
  5. 基本数据类型的打印变量都已经实现了。

rust中的变量默认是不可变的

不可变性是rust实现其可靠性和安全性目标的关键。该方式可以让程序员了解程序状态的变化,并明确哪些部分的程序状态可能会发生变化。

如果要让一个变量是可变的,可以使用mut关键字来实现。

1
2
let mut y = 10;
y = 20;

Shadowing Variables

Rust可以隐藏一个变量,因此可以声明一个与现有变量同名的新变量,该变量会隐藏之前的老变量。

1
2
3
let x = 10;
// ...
let x = 2.3;

const 常量

  • 常量的值必须在编译时已知的常量表达式,必须指定类型与值。
  • 与C语言的宏定义不同,rust的const常量的值被直接嵌入到生成的pqnf机器代码中,而不是进行简单的字符替换。
  • 常量名与静态变量命名必须全部大写,单词之间加入下划线
  • 常量的作用域是块级作用域,它们只在声明它们的作用域内可见。

static静态变量

  • 与const常量不同,static变量是在去年时分配内存的
  • 其可以修改,可以使用unsafe修改,如果要打印,则也要在unsafe代码中打印
  • 静态变量的生命周期为整个程序的运行时间

基本数据类型

  1. 带符号的整数(默认为i32)
    1. i8,i16,i32,i64,i128
  2. 不带符号的整数
    1. u8,u16,u32,u64,u128
  3. 由平台决定的
    1. usize,isize
  4. 浮点型(推荐i64)
    1. f32,f64
  5. 布尔型
    1. true, false
  6. 字符类型
    1. 支持unicode字符
    2. char类型要使用单引号

元组与数组

  • 相同点:
    • 元组与数组都是Compound Types(多个值组合在一起形成的类型),而Vec与Map都是Collection Types(可以包含多个值的动态大小的数据结构)
    • 元组和数组长度都是固定的
    • 均可以使用mut来设置为可变的。对于元组来说,修改前后的数据类型要保持一致。
  • 不同点
    • 元组:可以是不同的数据类型
    • 数组:必须是相同的数据类型

数组

  • 数组是固定长度的同构集合
  • 创建方式
    • [a,b,c]
    • [value; size]
  • 获取元素:arr[index]
  • 获取长度:arr.len()

元组

  • 元组是固定长度的异构集合
  • Empty Tuple()
    • 为函数的默认返回值
  • 元组获取元素
    • tup.index
    • 没有len()

内存管理模型

  1. 纯给程序员(c++)
  2. 纯给gc(java)
  3. rust型
    1. 在编译时期做一系列的检查,如果检查到有问题,则不会通过
    2. 使用所有权机制来限制错误的产生

rust的内存管理模型

  • 所有权系统(ownership system)
  • 借用(borrowing)
    • 不可变借用
    • 可变引用
  • 生命周期(lifetimes)
  • 引入计数(reference counting)

表示字符串的两种类型

String

string是一个堆分配的可变字符串类型

  • String是具有所有权的
  • Struct中属性使用String
    • 如果不使用显式声明生命周期无法使用&str
    • 会带来很多隐患

&str

&str是指字符串切片的引用,是在栈上分配的

  • 是不可变引用,指向存储在其他地方的UTF-8编码的字符串数据
  • 由指针与长度构成
  • 函数参数推荐使用&str(如果不想交出所有权)
    • &str为参数,可以传递&str与&String
    • &String为参数,只能传递&String不能传递&str

Enum枚举

  • 枚举是一种用户自定义的数据类型,用于表示具有一组离散可能值的变量
    • 每种可能值都称为”variant”
    • 使用的方式为枚举名::变体名
  • 枚举的好处
    • 可以使代码更严谨,更易读
    • 更加健状

常用的枚举的类型:OptionResult

匹配模式

枚举常与匹配模式一起用

  1. match关键字实现
  2. 必须覆盖所有的变体
  3. 可以用 _, ..=, 三元等来进行匹配

结构体

结构体是一种用户定义的数据类型,用于创建自定义的数据结构。每条数据称为属性,通过(.)来访问结构体中的属性。

结构体中的方法(本质是一个关联函数)

这里的方法是指,通过实例调用(&self, &mut self, self)

结构体中的关联函数(类似于c++中的静态成员函数)

关联函数是与类型相关联的函数,调用时为结构体名::函数名

结构体中的关联变量(类型于c++中的静态成员变量)

这里的关联变量是指,和结构体类型相关的变量,也可以在特质或是枚举中调用。

Ownership所有权机制

所有权机制的规则

  • 所有的值都只有一个拥有者
  • 同一时刻,只能有一个所有者
  • 当值超出了所用域以后,会自动的进行消除

rust中传值的方式

每当将值从一个位置传递到另一个位置时,borror checker都会重新评估所有权。

  1. 不可变借用:值的所有权仍归发送方所有,接收方直接接收对该值的引用,而不是该值的副本。倡,他们不能使用该引用来修改它指向的值,编译器不允许这样做。释放资源的责仍由发送方承担。仅当发件人本身超出满园时,才会删除该值。
  2. 使用可变的借用所有权和删除值的责任也由发送者承担。但是接收方能够通过他们接收的引用来修改该值。
  3. move:这是所有权从一个地点转移到另一个地点。borrow checker关于释放该值的决定将由该值的接收者通知。由于所有权已从发送方转移到接收方,因此发送方在将引用移动到另一个上下文后不能再使用该引用,发送方在移动后对value的任何使用都会导致错误。

以下是三种self的区别:

1
2
&self 等于 (self: &Self)
不可变引用
1
2
&mut self等于(self:&mut Self
可变引用
1
2
self等于(self: Self
move

堆与栈

stack

  1. 堆栈将按照获取值的顺序存储值,并以相反的顺序删除值
  2. 操作高效,函数伤域就是在栈上
  3. 堆栈上存在的所有数据都必须具有已知的固定大小数据

在栈上的数据有:

  1. 基础类型
  2. tuple与array
  3. struct与枚举等,如果属性中有String等存储在堆上的数据类型时,会将其指向堆

heap

  1. 堆的规律性较差,当把一些东西放到请求的堆上时,会返回一个指针
  2. 长度不确定

在堆上的数据有:

  1. Box,Rc,String/Vec等

Box

Box是一个智能指针,它提供对堆分配内存的所有权。它允许将数据存储在堆上而不是栈上,并且在犁或移动时保持对数据的唯一拥有权。使用Box可以避免一些内存管理问题,如悬垂指针与重复释放

Box的主要的4个作用:

  1. 所有权转移
  2. 释放内存
  3. 解引用
  4. 构建递归数据结构

copy与clone

  • move:所有权转移
  • clone:深拷贝
  • copy:在clone的基础上建立的标记特质(marker trait,类似于继承的关系)

特质(trait):

  1. 特质是一种定义共享行为的机制。clone也是特质。
  2. marker trait是一个没有任何方法的trait,它主要用于向编译器传递某些信息,以改变类型的默认行为。

一般来说在栈上的数据类型都默认copy,但struct 等默认为move,需要copy只需要设置数据类型实现copy特质即可,或是调用clone函数(需要实现clone特质)

流程控制与函数

流程控制

  • 顺序结构
  • 选择结构(if, switch)
  • 循环结构(for,while, do-while)
  • 跳转结构(break, continue, goto)

IF流程

  • 执行流程可以被IF改变
  • 可以嵌套使用

match表达式

  • 用于模式匹配,允许更复杂的条件与分支
  • 可以处理多个模式,提高代码的表达力
  • 是表达式,可以返回值

两者对比

  • 复杂性:if适用于简单的条件判断,而match更适用于复杂的模式匹配

  • 表达力:match更灵活,可以处理多个条件与模式,使代码更清晰

  • 返回值:两者都是表达式,可以返回值,但match通常用于更复杂的场景

循环

  • loop循环:是一个无限循环
  • while循环:与c语言一样
  • for循环:与python类似

break 与 continue

break关键字用于立即编目循环,并跳出循环体

continue关键字用于立即跳过当前循环中剩余的代码,进入下一次循环

迭代

rust中的迭代主要通过迭代器实现, 迭代器是一个抽象,提供了一种访问集合元素的统一方式。

从实现上讲,在rust中,迭代器是一种实现了iterator trait的类型

循环与迭代的不同

循环适用于需要明确控制循环流程的情况,而迭代器则提供了一种更抽象的方式来处理集合元素。通常,推荐使用迭代器,因为它们可以提高代码的可读性和表达力。

for循环是一种语法结构,用于遍历集合中的元素,它依赖于集合类型实现Iterator trait.

在rust中,迭代器提供了一系列用于遍历集合元素的方法,比如next()map()filter()等,可以让我们的代码更具有表达性。

函数

函数的定义:在rust中,可以使用fn关键字声明与定义函数,而main是程序的入口点的一种特殊的函数

参数与返回值:

  • 函数可以接受零个或多个参数,每个参数都需要指定类型
  • 函数可以有返回值,使用->指定返回值类型。如果函数没有返回值,可以使用->()或省略这部分。

调用函数:调用函数时,使用函数名和传递给函数的实际参数。

copy by value

  • 如果数据类型实现copy特质,则在函数传参时会实现copy by value操作。
  • 会将实参拷贝为形参,形参的改变不会影响实参
  • 如果要改变形参,需要添加mut

Struct ,枚举,集合等并没有实现copy trait,会实现move操作,会失去所有权

如果为数据类型实现了copy trait,则可以实现copy by value

函数值参数传递(move)

函数的代码本身通常是存储在可执行文件的代码段,而在调用时函数会在栈上开辟一个新的stack frame,用于存储函数的局部变量,参数和返回地址等信息,而当函数结束后会释放该空间。

而当传入non-copy value(Vec, String等)

  • 传入函数时实参会转移value的所有权给形参,实参会失去value的所有权
  • 而在函数结束时,value的所有权会释放

不可变借用

  • 如果不想失去value的所有权,又没有修改value的需求,可以使用不可变借用
  • 在rust中,可以将不可变引用作为函数的参数,从而在函数内部访问参数值但不能悠它。这有确保数据安全性,防止在多处同时对数据进行写操作,从而避免数据竞争。
  • 如何使用不可变借用
    • use * to deference,去获取以其的值

可变借用

  • 如果有修改值的需求,可以使用可变借用,以允许在函数内部修改参数的值。这允许函数对参数进行写操作,但在同一时间内只能有一个可变引用。

  • 需要在前加&mut

  • 如何使用可变借用

    • use * to deference,去获取以其的值

返回copy 与non-copy

以上两种类型的值都可以返回,而non-copy则是在堆上返回

一般来说,返回copy类型的值通常有更好的性能。因为copy类型的值是通过复制进行返回的,而不涉及堆上的内存的分配与释放,通常是在栈上分配。这样的操作比涉及在堆上的内存的分配和释放更为高效。

返回引用

在只有传入一个引用参数,只有一个返回引用时,生命周期不需要声明

其他情况下需要声明引用的生命周期

慎用'static'

高阶函数

高阶函数:rust使用高阶函数,即函数可以作为参数传递给其他函数,或者函数可以返回其他函数

高阶函数也是函数式编程的重要特性。

高阶函数与集合

  1. map函数:可以用于对一个集合中的每个元素应用一个函数,并返回包含结果的新集合。
  2. filter函数:用于过渡集合中的元素,根据一个谓词函数的返回值
  3. fold:该函数也可称为reduce,可以用于迭代集合的每个元素,并将它们累积到一个单一的结果中。

错误处理

rust中的错误

错误可以分为两种:

  • recoverable error:有返回类型
    • 返回Result类型
    • 返回Option类型
  • unrevocerable type:没有返回类型,直接崩溃
    • panic macro将终止当前线程

Result

Result是一个枚举类型,有两个变体:Ok与Err。它通常用于表示函数的执行结果,其中Ok表示成功的结果,Err表示出现了错误

Option

Option也是一个枚举类型,有两个变体:some与None,它通常用于表示一个可能为穿的值。

panic!

当程序遇到无法继续执行的错误时,可以使用panic!宏来引发恐慌。恐慌会导致程序立即终止,并显示一条错误消息。

错误处理

unwrap()

该方法并不安全

unwrap()是Result与Option类型提供的方法之一。它是一个简便的方法,用于获取Ok或Some的值,如果是Err或None则会引发panic

?运算符

?用于简化Result或Option类型的错误传播。它只能用于返回Result或Option的函数中,并且在函数内部可以像使用unwarp()一样访问Ok或Some的值,但是如果是Err或None则会提前返回。

自定义Error

自定义的Error的三个步骤

  1. 定义错误类型结构体:创建一个结构体来表示你的错误类型,通常包含一些字段来描述错误的详细信息
  2. 实现std::fmt::Display trait,实现这个trait以定义如何展示错误信息。这是为了使错误可以以人类可读的方式打印出来。
  3. 实现std::error::Error trait:实现这个trait以满足Rust的错误处理机制的要求。