Page 186 - 《软件学报》2025年第8期
P. 186

张卓若 等: 面向   Rust 语言的形式化验证方法研究综述                                                 3609



                                                                            1   let mut foo: T = ...;
                                                                            2   let mut bar: T = ...;
                                                                            3   let mut p: &T;
                                                                            4   p = &foo;
                                                                            5   if condition {
                                                                            6    print(*p);
                                                                            7    p = &bar;
                                                                            8   }
                                                                            9   print(*p);
                                                图 4 Rust 代码例子和程序流图

                                      1  fn get_or_insert(map: & mut HashMap<u32, String>) -> &String {
                                      2    match HashMap::get(&*map, &22){
                                      3      Some(v) => {v}
                                      4      None => {
                                      5        HashMap::insert(&mut *map, 22, String::from("hi"));
                                      6        &map[&22]
                                      7      }}}
                                                 图 5 Polonius 分析复杂借用

                    实际上, Rust 提供了多种借用形式, 每种形式都有其特定的用途和规则, 适用于不同的具体情况. 例如, 返回借
                 用  (return borrows) 允许函数返回对传入引用参数的借用, 但要求借用的生命周期至少与返回值一样长; 嵌套借用
                 (nested borrows) 即一个借用内部又包含了对另一个值的借用的情况, 这要求内部借用的生命周期不超过外部借用;
                 两阶段借用    (two-phase borrows) 允许在不同时间点进行可变借用和不可变借用, 但不允许同时存在, 以避免数据
                 竞争等问题. 这些借用形式共同构成了            Rust 灵活而强大的借用检查系统.

                 2.2   Rust 复杂特性

                 2.2.1    unsafe Rust 代码
                    Rust 作为一种现代的系统编程语言, 在设计上除了考虑内存安全保证外, 同时还需要保持高性能. 尽管                              Rust
                 的大部分代码都是用所谓安全子集            (safe subset) 编写的, 并通过  Rust 所有权系统和借用检查器提供强大的内存安
                 全保证, 但在某些情况下, 开发者需要绕过这些安全检查以直接访问底层系统资源或优化性能. 为此, Rust 允许开
                 发者在明确了解潜在风险的情况下编写              unsafe 代码块, 与底层操作系统交互或提供低级别的抽象, 并获得一些超
                 越常规   Rust 安全保证的特殊能力. Unsafe 代码块主要允许以下几种操作: 解引用原始指针                     (raw pointer)、调用
                 unsafe  函数或方法  (包括外部    C/C++函数)、访问或修改可变静态变量、实现非安全特质                   (unsafe traits)、读写
                 union  联合体中的字段. Unsafe 代码通常用于性能优化、低级别操作和与其他语言的接口等场景.
                    Unsafe 代码是  Rust 设计中不可或缺的一部分, 它使得安全抽象能够在库中高效实现. 例如双向链表类型的实
                 现就需要利用     unsafe 代码来达到高效的内存管理和操作. 然而, 对            unsafe 机制的不当使用容易导致安全漏洞, 形
                 成对  Rust 程序的新攻击面. 如图      6  的例子是使用   unsafe Rust 的典型示例. 原始指针提供了类似于         C  语言中指针
                 的操作能力, 例如进行指针算术. 与         Rust 中的引用不同, 原始指针不会被借用检查器所跟踪. 在               Rust 中, 解引用原
                 始指针是一种不安全的操作, 因为对于原始指针, 类型系统无法确保它们是否指向了有效的、可以安全访问的内
                 存位置. 如果原始指针指向的内存不是有效的, 或者该内存没有被正确初始化, 那么尝试解引用它将可能导致程序
                 崩溃, 例如触发段错误      (segmentation fault). 图  6  的第  5  行会因越界访问导致未定义行为  (undefined behaviour), 但
                 由于  unsafe 代码可以绕过   Rust 编译器的检查, 因此不会报错.
   181   182   183   184   185   186   187   188   189   190   191