大家好,我是梦兽编程。点进来的多半是因为看到谷歌的字眼吧。没错Rust系列将以谷歌安卓团队分享的Rust经验进行持续更新。

这是由 Google 的 Android 团队开发的免费 Rust 课程。本课程涵盖了 Rust 的方方面面,从基本语法到泛型和错误处理等高级主题。

该课程的最新版本可以在 https://google.github.io/comprehensive-rust/找到。如果您正在其他地方阅读,请检查那里的更新。

在梦兽编程开了这么多rust的教程中,谷歌安卓团队推出的rust经验分享,可以说是简单明了。每个分享都是突出重点没有一句多余的废话。新手入门也可以很容易的掌握rust的“生命周期”,“借用”,“引用”。

但是谷歌安卓团队这个分享都是一些PPT内容,这里就由梦兽编程详细的补充。

如果你之前没有了解过Rust,本次Rust专栏分享希望你可以做到以下几点:

  1. 让你全面了解Rust的语法和语言
  2. 能够阅读或参与GitHub上优秀的Rust项目

什么是Rust

Rust是一门新的编程语言,最早是2015年发布了1.0的版本。Rust是静态编译语言与C++类似。Rust使用LLVM 作为作为工具链集合。Rust和C++类似也能运行在各大平台,而且也能运行在嵌入式应用中。

Rust与C++一样没有运行时和垃圾回收,代码中的内存交由开发者进行管理。所以Rust的性能几乎与C++一致。只不过C++的性能取决编写代码的人能力,如果C++水平不高的人写出来的代码依然是很慢的

Rust作为一门21世纪的新语言,它很多时候都是由编译器帮我们处理好了,Rust的性能85%的情况下是有编译器帮我们处理的。正因为编译器帮我们做了很多活,Rust同时也帮我们处理C++大多数头条的指针问题

Rust 注重可靠性和安全性,同时不牺牲性能。

Hello Word

让我们跳到最简单的 Rust 程序,一个经典的 Hello World 程序:

fn main() {
    println!(""Hello 🌍!"");
}

在这个例子中,我们使用fn这个关键词定义rust语言中的函数。几乎所有的静态编译语言都会存在一个main的主入口函数Rust也不其然。

println!是Rust语言提供的宏,宏这个概念在C中并不陌生。一般带有GC的语言是不提供这种功能。println!与GC语言(比如Golang中的fmt是两个概念)。Rust 在您希望拥有可变数量的参数(无函数重载)的情况下使用宏。

宏是不容易被污染的,这意味着它们不会意外地捕获来自它们被使用的范围的标识符。这使得 Rust 中的宏更加安全和可靠。

Rust 是多范式的。例如,它具有强大的面向对象编程功能,并且虽然它不是一种函数式语言,但它包含了许多函数式概念。

梦兽编程是比较喜欢使用函数式的,这个看个人习惯。

一个小例子进阶

fn main() {              // Program entry point
    let mut x: i32 = 6;  // Mutable variable binding
    print!("{x}");       // Macro for printing, like printf
    while x != 1 {       // No parenthesis around expression
        if x % 2 == 0 {  // Math like in other languages
            x = x / 2;
        } else {
            x = 3 * x + 1;
        }
        print!(" -> {x}");
    }
    println!();
}

// output 6 -> 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1

在这个例子中,我们如果将代码中的let mut x :i32 = 6; 修改改成 let x:i32 = 6;这个程序会立马报错,在rust中如果没有mut这个关键字。那么rust会认为这个变量不是一个可变的参数,而是一个常量。定义是不可以进行修改的。

在上述的代码中print!(" -> {x}");同样可以修改成print!(" -> {}",x);的写法。

值得注意的是你需要明确{}的占位符,如果修改成print!(" -> {}",x,y);同样是无法进行编译的。

在上述代码中我们吧i32改成i8,并把 = 6的值修改到1000同样会报错,这里需要注意类型的取值范围,与大多数静态编译语言类似。

在开发的过程中,比如print是在std::fmt的package下,如果你想查看更多关于std::fmt的内容可以在控制台中执行rustup doc std::fmt,运行后会跳到浏览器客户端查看详细的文档,这个过程中需要你有一点英语能力。

rustup doc std::fmt

为什么使用Rust

Rust的独特特点:

  • 编译时内存安全。
  • 缺少未定义的运行时行为。
  • 现代语言功能。与C和C++的CMake工具链相比Rust的工具链相当优秀。当然C++也有比较好的解决方案(微软解决方案,当需要你在VS开发工具使用只能在window上进行开发)

如果你有c和c++开发经验,那么它们的语法特性让你写出空指针的情况是非常多的。就golang来说你也有可能在运行时写出空指针。但Rust通过借用编译器消除了一整类运行时错误,让你拥有与c、c++媲美的性能,但你内存不安全问题。此外,您还可以获得一种现代语言,其中包含模式匹配和内置依赖项管理等构造。

对于Java,Go,Python,JavaScript...的经验:您可以获得与这些语言相同的内存安全性,以及类似的高级语言感觉。此外,您还可以获得快速且可预测的性能,如 C 和 C++(无垃圾回收器)以及对低级硬件的访问,如果你有嵌入式开发的需求。

打个比方

我们查看最小错误示例的c代理例子,你能看出多少错误?

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int main(int argc, char* argv[]) {
    char *buf, *filename;
    FILE *fp;
    size_t bytes, len;
    struct stat st;

    switch (argc) {
        case 1:
            printf("Too few arguments!\n");
            return 1;

        case 2:
            filename = argv[argc];
            stat(filename, &st);
            len = st.st_size;
            
            buf = (char*)malloc(len);
            if (!buf)
                printf("malloc failed!\n", len);
                return 1;

            fp = fopen(filename, "rb");
            bytes = fread(buf, 1, len, fp);
            if (bytes = st.st_size)
                printf("%s", buf);
            else
                printf("fread failed!\n");

        case 3:
            printf("Too many arguments!\n");
            return 1;
    }

    return 0;
}

虽然只有29行,但是这里的代码中存在9个致命的错误。

  1. 赋值 = 而不是相等比较 == (第 28 行)
  2. (第 23 行)的 printf 多余参数
  3. 文件描述符泄漏(第 26 行之后)
  4. 多行 if 中被遗忘的大括号(第 22 行)
  5. 在 switch 声明 break 中被遗忘(第 32 行)
  6. 忘记字符串的 buf NUL 终止,导致缓冲区溢出(第 29 行)
  7. 通过不释放 分配的 malloc 缓冲区而导致内存泄漏(第 21 行)
  8. 越界访问(第 17 行)
  9. switch 语句中未选中的案例(第 11 行)
  10. stat 未经检查的返回值 and fopen (第 18 行和第 26 行)

即使对于 C 编译器来说,这些错误也不应该很明显吗?出乎意料的是,这段代码在最新版本的 GCC(截至写作时为 13.2)中,即使在默认警告级别下也能编译为无警告。

这不是一个非常不切实际的例子吗?

绝对不是,这些错误在过去会导致严重的安全漏洞。一些例子:

  1. 赋值 = 而不是相等比较 == :The Linux Backdoor Attempt of 2003
  2. 多行 if 中被遗忘的大括号:The Apple goto fail vulnerability
  3. break 在声明 switch 中被遗忘:The break that broke sudo

Rust 在这里如何更好?
以上C语言的代码中,我们可以看出很多时候因为我们的不注意导致我我们肉眼看不出来,rust为人让代码看起来更加直观做出以下调整:

  1. 不支持 if 子句内的赋值。
  2. 格式字符串在编译时检查。
  3. 资源在范围结束时通过 Drop 特征释放。这里需要知道作用域后释放,这对你学习生命周期时候有很多帮助
  4. 所有 if 子句都需要大括号。
  5. match (因为 Rust 等效于 switch )不会掉落,因此您不会意外忘记 break ,Rust中推荐使用match而不是switch
  6. 缓冲区切片具有其大小,不依赖于 NUL 终止符。
  7. 堆分配的内存在相应 Box 内存离开作用域时通过 Drop 特征释放。
  8. 越界访问会导致恐慌,或者可以通过切片 get 方法进行检查。
  9. match 要求处理所有案件。也就是所有的情况的都需要匹配上,包括后面学习到的None
  10. 易出错的 Rust 函数会返回 Result 值,该值需要被拆包并检查是否成功。此外,如果忘记检查标有 #[must_use] 属性的函数的返回值,编译器会发出警告。

好了今天先将这么多,如果你非常喜欢本栏目可以进入https://rexweb.link/category/technical-column/learn-rust-from-the-android-team或者更多技术可以收藏梦兽编程https://rexweb.link

梦兽编程倔强号
梦兽编程知乎
梦兽编程bilibili

微信搜索梦兽编程公众号

梦兽编程倔强号

https://juejin.cn/user/2066737588876983

梦兽编程知乎

https://www.zhihu.com/people/mo-dong-74

梦兽编程bilibili

https://space.bilibili.com/106325238?spm_id_from=333.1007.0.0


梦兽编程
1 声望0 粉丝