type
Post
date
Oct 29, 2025
slug
Rust/notebook/3
summary
学习下 Rust 圣经,记录笔记
status
Published
tags
Rust
category
技术茶点
icon
password
😀
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。

3.在Rust上下文中探讨常见概念

3.1.变量与可变性

  • 使用 mut 让变量可变
    • 是否让变量可变的最终决定权仍然在你,取决于在某个特定情况下,你是否认为变量可变会让代码更加清晰明了
  • 常量
    • 区别于变量
      • 不允许使用 mut
      • 默认不变,且总是不变
      • const 声明,而不是 let,并且必须注明值的类型
      • 常量可以在任何作用域中声明,包括全局作用域
      • 在声明它的作用域之中,常量在整个程序生命周期中都有效
      • 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
        • 命名约定是在单词之间使用全大写加下划线
    • 遮蔽
      • 此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被遮蔽或第二个变量的作用域结束
        • “变量遮蔽(shadowing)”,它和 mut可变绑定 是两种完全不同的机制
        • shadowing 的好处是:
          • 仍然保持不可变(每次都新建一个新变量);
          • 但允许你在不同阶段“复用名字”,方便表达计算过程;
          • 并且你可以改变类型,比如:
            • 这行得通,但 mut 不行,因为 mut 只能让同一个变量修改值,不能变类型

        3.2.数据类型

        • 数据类型data type
          • 在 Rust 中,每一个值都有一个特定 数据类型,这告诉 Rust 它被指定为何种数据
          • 两类数据类型子集:标量(scalar)和复合(compound)
          • Rust 是 静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型
          • 当多种类型均有可能时,必须增加类型注解

          3.2.1.标量类型

          代表一个单独的值,有4种,包括:整型、浮点型、布尔类型和字符类型。
          • 整型
            • 原文比特位的说法
              • 占据 32 比特位,这个整数类型在内存中用 32 个二进制位(bit) 来表示它的值。
              • 比特位(bit) 当作 “一个灯泡”,只有亮(1)或灭(0)两种状态;
              • 字节(byte) 当作 “一组灯泡”,通常 8 个灯泡一组。
              • u8 类型就是有 8 个灯泡,能亮灭出 2⁸ = 256 种状态。
              • 也就是说,“比特位” 就是“二进制位”,只不过是两种说法的翻译差异。
              • 长度
                有符号
                无符号
                8-bit
                i8
                u8
                16-bit
                i16
                u16
                32-bit
                i32
                u32
                64-bit
                i64
                u64
                128-bit
                i128
                u128
                架构相关
                isize
                usize
                类型
                位数 n
                取值范围
                u8
                8
                0 ~ 255
                i8
                8
                -128 ~ 127
                u16
                16
                0 ~ 65535
                i16
                16
                -32768 ~ 32767
                u32
                32
                0 ~ 4,294,967,295
                i32
                32
                -2,147,483,648 ~ 2,147,483,647
            • 有符号数以二进制补码形式(two’s complement representation) 存储
            • 每一个有符号的变体可以储存包含从 -(2n - 1) 到 2n - 1 - 1 在内的数字,这里 n 是变体使用的位数。所以 i8 可以储存从 -(27) 到 27 - 1 在内的数字,也就是从 -128 到 127。
              • 无符号的变体可以储存从 0 到 2n - 1 的数字,所以 u8 可以储存从 0 到 28 - 1 的数字,也就是从 0 到 255。
            • isize 和 usize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。
            • Rust 中的整型字面值
              • 数字字面值
                例子
                Decimal (十进制)
                98_222
                Hex (十六进制)
                0xff
                Octal (八进制)
                0o77
                Binary (二进制)
                0b1111_0000
                Byte (单字节字符)(仅限于u8)
                b'A'
            • Rust 的默认类型通常是个不错的起点,整型默认是 i32
            • 关于整形溢出
              • 方法名
                含义
                示例
                结果
                wrapping_add
                回绕(模运算)
                255 + 1
                0
                checked_add
                检查(返回 Option)
                255 + 1
                None
                overflowing_add
                返回溢出标志
                255 + 1
                (0, true)
                saturating_add
                饱和(卡死在边界)
                255 + 1
                255
          • 浮点型
            • Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64
            • 数值运算
              • let 语句中使用各种整数运算
                • 整数除法
                  • 结果会 舍弃小数部分(也叫“截断”)
                  • rust 采用的规则是:向零截断(truncation toward zero)
            • 布尔类型
              • bool
              • 字符类型
                • char
                  • 用单引号声明 char 字面值

              3.2.2.复合类型

              Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
              • 元组
                • 元组长度固定:一旦声明,其长度不会增大或缩小。
                  • 为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值
                    • 也可以使用点号(.)后跟值的索引来直接访问所需的元组元素
                      • 不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。
                        • 如果表达式不返回任何其他值,则会隐式返回单元值。
                    • 数组类型
                      • 数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块
                      • 数组中的每个元素的类型必须相同
                      • Rust 中的数组长度是固定的
                        • 当你 确定数量不会变(比如一周 7 天、月份 12 个)时,就用数组
                          • 编写数组的类型
                            • 设定相同的初始值
                              • 使用索引访问数组的元素
                              • 当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 panic,这是 Rust 术语,它用于程序因为错误而退出的情况。
                                • Rust 在运行时检查数组边界,防止非法内存访问,这是 Rust 安全设计的核心体现。
                          • vector(不是原声类型,标准库 std 提供的的一种集合类型)
                            • 特性
                              元组 ()
                              数组 [T; N]
                              向量 Vec<T>
                              存放位置
                              栈(stack)
                              栈(stack)
                              堆(heap)
                              长度
                              固定
                              固定
                              可变
                              元素类型
                              可不同
                              必须相同
                              必须相同
                              用途
                              打包不同类型的数据
                              存放固定数量的同类型数据
                              存放动态数量的同类型数据
                              示例
                              (1, "hi", true)
                              [1, 2, 3]
                              vec![1, 2, 3]
                            • 当你 不知道未来有多少数据,或者需要频繁增删时,用 vector
                            • 可以 动态增长或缩小
                            • 放在堆上(系统动态管理空间)
                            • 稍慢,但灵活得多

                          3.3.函数

                          3.3.1.回顾

                          • main 函数,它是很多程序的入口点
                          • 也见过 fn 关键字,它用来声明新函数
                          • snake_case 风格
                            • 源码中 another_function 定义在 main 函数 之后;也可以定义在之前。
                            • Rust 不关心函数定义所在位置,只要函数被调用时出现在调用之处可见的作用域内就行。
                              • Rust 编译器在编译时,会先扫描整个文件,收集所有函数、类型、模块等的定义
                              • 如果函数定义在不同的模块或文件中,就要通过模块路径访问或显式 pub 公开。

                            3.3.2.参数

                            • 在函数签名中,必须 声明每个参数的类型

                              3.3.3.语句和表达式

                              • 语句Statements)是执行一些操作但不返回值的指令。
                                • 语句不返回值。因此,不能把 let 语句赋值给另一个变量
                                  • 在 python 中,可以这么写 x = y = 6,这样 x 和 y 的值都是 6;Rust 中不能这样写
                              • 表达式Expressions)计算并产生一个值。
                                • 语句 let y = 6; 中的 6 是一个表达式,它计算出的值是 6。函数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式
                                  • 注意 x + 1 这一行在结尾没有分号,与你见过的大部分代码行不同。表达式的结尾没有分号
                                  • 如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值
                              • 具有返回值的函数
                                • 函数可以向调用它的代码返回值
                                • 并不对返回值命名,但要在箭头(->)后声明它的类型
                                • 函数的返回值等同于函数体最后一个表达式的值
                                  • 使用 return 关键字和指定值,可从函数中提前返回
                                  • 但大部分函数隐式的返回最后的表达式
                                  • let x = five(); 这一行表明我们使用函数的返回值初始化一个变量
                                • 错误情况
                                  • 函数 plus_one 的定义说明它要返回一个 i32 类型的值,不过语句并不会返回值,使用单位类型 () 表示不返回值

                              3.4.注释

                              在 Rust 中,惯用的注释样式是以两个斜杠开始注释,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 //

                              3.5.控制流

                              3.5.1.if 表达式

                              • if / else
                                • 如果不提供 else 表达式并且条件为 false 时,程序会直接忽略 if 代码块并继续执行下面的代码。
                                • 代码中的条件必须是 bool 值
                                  • Rust 并不会尝试自动地将非布尔值转换为布尔值
                                  • 必须总是显式地使用布尔值作为 if 的条件
                                • else if 处理多重条件
                                  • 示例
                                    • 即使 6 可以被 2 整除,也不会输出 number is divisible by 2,更不会输出 else 块中的 number is not divisible by 4, 3, or 2。原因是 Rust 只会执行第一个条件为 true 的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了
                                    • 当出现多于一个 else if 分支时,代码会变得混乱,最好使用更清晰的分支结构 —— match
                                • 在 let 语句中使用 if
                                  • 因为 if 是一个表达式,我们可以在 let 语句的右侧使用它
                                    • 整个 if 表达式的值取决于哪个代码块被执行。这意味着 if 的每个分支的可能的返回值都必须是相同类型
                                      • Rust 需要在编译时就确切的知道 number 变量的类型
                                      • 如果number的类型仅在运行时确定,则 Rust 无法做到这一点

                                3.5.2.使用循环重复执行

                                Rust 有三种循环:loopwhile 和 for
                                • loop
                                  • 一遍又一遍地执行一段代码直到你明确要求停止
                                    • 使用 break 关键字来告诉程序何时停止循环
                                    • 循环中的 continue 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代
                                  • 从循环返回值——比如检查线程是否完成了任务
                                    • 停止循环的 break 表达式后添加你希望返回的值
                                  • 循环标签——在多个循环之间消除歧义
                                    • 循环标签
                                      • Rust 允许给循环(loop while for)加一个标签,用于在嵌套循环精确控制要 breakcontinue 哪一层循环
                                • while 条件循环
                                  • src/main.rs 示例
                                    • 这种结构消除了很多使用 loopifelse 和 break 时所必须的嵌套,这样更加清晰。当条件为 true 就执行,否则退出循环
                                • 使用 for 遍历集合(Rust 中使用最多的循环结构)
                                  • src/main.rs 示例
                                    • 尽管 index 在某一时刻会到达值 5,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了,但这个过程很容易出错
                                    • 程序更慢,因为编译器增加了运行时代码来对每次循环进行条件检查
                                    • 作为更简洁的替代方案,可以使用 for 循环
                                      • 增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug
                                  • 用 for 循环实现 while 循环倒计时
                                  • 课后题
                                   
                                  《Rust 程序设计语言》(4/22)《Rust 程序设计语言》(2/22)
                                  Loading...