初识 Rust 语言

220 阅读9分钟

是否要学 Rust

image

image

octoverse.github.com/2022/top-pr…

对于我们熟知的一些语言如 C++,Java,JavaScript 等,Rust 是一门较新的语言,上可开发Web App,下可写底层操作系统。但我们谈论编程语言时,从来没有一个放之四海而皆准的解决方案,编程语言只是工具,主要看它的特点和优势在哪里,最好不要盲目地学习,不然可能会出现下面的场景:

学完 Python,Golang 火了,开始学 Golang ,Rust 火了。

Rust 的起源

Rust 语言最初只是 Mozilla 员工 Graydon Hoare 在 2006 年创建的一个业余项目。在 2010 年时,Mozilla 的研发团队在 Firefox 的开发过程中遇到了瓶颈。作为一个大型的自研浏览器项目,Firefox 底层实现主要用 C++ 编写。尽管团队拥有最佳的实践经验和丰富的工程技术人才,但是在编写浏览器内核这样拥有如此复杂的高性能、并行化和内存安全要求的代码时,仍然很容易出错。据统计,Firefox 中有超过 70% 的漏洞与内存安全有关。

image

正是在这种情况下,时任 Mozilla 工程师的 Graydon Hoare 毛遂自荐,掏出了自己捣鼓了 4 年的新语言 Rust 。在 JS 之父 Brendan Eich 和 Dave Herman 等人的帮助下,经过团队的进一步打磨,Rust 语言也从最初的个人项目变成一个日趋成熟的现代化编程语言。2012 年,Mozilla 正式宣布将推出基于 Rust 语言开发的新浏览器引擎 Servo,这是一个以内存安全性和并发性为首要原则而构建的浏览器引擎,同时也是 Rust 语言在一个完整大型项目中的首秀。

第一个有版本号的 Rust 编译器也于 2012 年 1 月发布。 Rust 1.0 的第一个稳定版本于 2015 年 5 月发布。

Rust 的特性

image

  1. 没有运行时环境和垃圾回收

Rust 程序的编译和运行是单独的两步,编译期间会连接对应操作系统的库,使用对应操作系统提供的功能,最终生成二进制文件。运行的时候在操作系统的视角里就是一个进程,只有进程退出,操作系统才会回收其占用的内存。

  1. 所有权模型 —— Rust 的核心特性

所有的程序在运行时都必须管理他们使用计算机内存的方式:

  • 有些语言有垃圾回收机制,在程序运行中不断寻找不在使用的内存

  • 有些语言中,程序员必须显式地分配和释放内存

而Rust 独特的所有权机制,使得 Rust 可以在编译时基于这套规则时检查内存的使用情况,这也是 Rust 内存安全的核心。

Rust 的所有权是为了配合其的清晰的内存管理理念产生的。内存是实际的东西,变量是对内存的封装和抽象,Rust 的变量和其他语言的变量相比其抽象的方式不同,它多了一些东西,就是利用所有权和生命周期的概念,强迫你显式地标明了变量背后内存的持有和释放。

这种从语法层面的限制可以让编译器可以清晰地读懂一个函数在栈上做了些什么,需要多少栈内存,变量之间在整个流程上的关系,这样可以优化出极其高效的目标码。

Rust 可以来干什么

image

Rust 对于前端的意义

  • 前端开发

  • 前端工具开发

  • 全栈开发

  • 拓宽技术边界

Rust 与 C++

Rust 语言是一个对标 C++ 的语言,这两者的详细对比可以参考:

简单来说,性能层面两者没有明显的差异,Rust 语言提供了高度简洁的抽象,使其高效、安全但难学。但 30 余年的发展使得 C++ 目前的地位仍然是难以撼动的。

当你开始使用 C++ 时,你可以将学习曲线视为一座山,而对于 Rust,则可以将其视为一堵墙。

image

image

上手整一整

image

(Rust 的使用者被称为 Rustacean,一只红色的螃蟹,很可爱不是么)

安装 Rust

image

进入官网的引导界面 www.rust-lang.org/learn/get-s… ,页面会根据你的操作系统为你推荐安装的方式

Rust 提供了一个可以在网页上试用 Rust 语言的 PlayGround,可以直接在上面练习一些基础的语法。

在 MacOS 系统上只需要输入命令后,等待其安装完成即可。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

在安装流程完成之后,可以在重新打开的 Terminal 中运行 rustc -V 就可以查看当前安装的 Rust 版本号和 commit 消息:

rustc -V
// rustc 1.65.0 (897e37553 2022-11-02)

这里的 rustc 即是一个命令行编译用的程序,可以通过 rustc ,而对于复杂的程序推荐使用 Cargo,这个也在安装的 Rust 的过程中安装好了。

安装时也会在本地安装一个可以离线浏览的文档,可以通过rustup doc打开。

在 vscode 环境开发 Rust 时,推荐安装 rust-analyzer 插件。

Rust 的构建和依赖管理工具:Cargo

作为 Rust 的构建和依赖管理工具,Cargo 也吸收了不少工具的优点。

Cargo 会帮我们处理很多开发中的任务,比如构建代码、下载依赖库并编译这些库,组织正确的参数去调用 rustc 完成编译任务。

我们可以通过 cargo -V 查看 Cargo 的版本

创建项目时可以使用 cargo new [dirname] 或在已存在的文件夹内执行 cargo init,如果你想要创建一个包而不是一个可执行程序,可以添加 --lib参数。

我们创建一个此时会 Cargo 会帮我们创建目录结构,这一切似乎似曾相识:

  • src 里是项目的源码

  • Cargo.toml 是 Cargo 的配置文件,包含项目的基本信息与依赖的包(在 Cargo 中称为 Crate,意思为箱子)

  • Cargo.lock 用来锁依赖包的版本

  • target 是编译后输出的产物

image

常用的一些命令:

  • cargo build 编译 src 中的代码,默认的编译产物中会包含一些开发用的调试代码,添加 --release可以编译出生产环境的产物。

  • cargo run 在我们创建出来的项目中,使用cargo run可以编译并运行 src 中的代码,若未修改源码后直接使用此命令,会直接运行上次编译后的可执行文件。

  • cargo check 在开发过程中,一次完整的编译可能耗时较长,此时可以使用 cargo check来检查代码,确保能够通过编译,此命令不会产生可执行文件。

  • cargo clean 删除本地的编译产物。

  • cargo update 可能和我们的一些习惯不同,在 cargo 创建的项目中添加依赖,需要手动更新 Cargo.toml 文件,如添加一行 time = "0.1.12" ,然后执行cargo update命令完成依赖的安装。

配置国内源

Rust 官方默认的 Cargo 源服务器为 crates.io,其服务器在北美,在国内访问较慢,此时可以设置为国内的一些源:

创建 HOME/.cargo/config 文件,在 config 文件内写入下述配置内容。

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
# 指定镜像
replace-with = '镜像源名' # 如:tuna、sjtu、ustc,或者 rustcc

# 注:以下源配置一个即可,无需全部

# 中国科学技术大学
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

# 上海交通大学
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"

# 清华大学
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

# rustcc社区
[source.rustcc]
registry = "https://code.aliyun.com/rustcc/crates.io-index.git"

"Hello, world!"

fn main() {
    println!("Hello, world!");
}

Rust 基础知识

let mut x = 5; // mut x: i32
let mut done = false; // mut done: bool

while !done {
    x += x - 3;

    println!("{}", x);

    if x % 5 == 0 {
        done = true;
    }
}
  • for
for x in 0..10 {
    println!("{}", x); // x: i32
}

所有权模型

所有权规则,对于使用堆内存的数据:

  1. 每一个数据绑定一个变量,变量是数据的所有者。

  2. 每一个数据同时只有一个所有者

  3. 当所有者离开作用域时,其绑定的数据对应的内存就会被 Rust 释放

  • 所有权
fn foo() {
    let v = vec![1, 2, 3];
}

// 当 v 进入作用域,一个新的 vector 在栈上被创建,并在堆上为它的 3 个元素分配了空间。
// 当 v 在 foo() 的末尾离开作用域,Rust 将会清理掉与向量(vector)相关的一切,甚至是堆上分配的内存。
// 这在作用域的结尾是一定会发生的。
  • 所有权的移动
let v = vec![1, 2, 3];
let v2 = v;

// 过程中创建的 vector 的所有权从 v 转移到了 v2
// 此时不能再访问 v 上的数据

// ps: 如果一开始 v 绑定了简单的值,如 let v = 5;
// 此时不会发生所有权的移动 v2 也将绑定一个新的 5
  • 所有权与函数
// 将值传递给函数会发生所有权的变化
fn main() {
    let str = String::from("铂雨最帅");

    // 此时将字符串的所有权从 str 传递给了函数 my_func
    // main 函数中的 str 绑定失效了
    my_func(str); 
}

fn my_func(some_string: String) {
    println!("print in my-func: {}", some_string);
}

// 函数在返回值的过程中也会发生所有权的转移
fn main() {
    // 此时将字符串的所有权从函数内的 str_from_func 传递给了 main 函数的 str
    let str = gen_string();

    println!("{}", str);
}

fn gen_string() -> String {
    let str_from_func = String::from("铂雨最帅");

    str_from_func
}

  • 所有权的引用
// 所有权的引用
fn main() {
    let str = String::from("铂雨最帅");

    // 此时将字符串的引用传递给了函数 my_func,所有权没有发生改变
    my_func(&str);

    // main 函数中的 str 可以正常使用
    println!("print in main: {}", str)
}

fn my_func(some_string: &String) {
    println!("print in my-func: {}", some_string);
}

// 引用的作用域是从声明到最后一次使用
// 这种以引用作为函数参数的行为,称为借用

所有权解决的问题:

  • 跟着代码的哪些部分正在使用堆内存上的哪些数据

  • 最小化堆内存上的重复数据量

  • 全量堆内存上未使用的数据以避免空间不足