《Rust 编程第一课》 学习笔记 Day 3

377 阅读5分钟

大家好,我是砸锅。一个摸鱼八年的后端开发。熟悉 Go、Lua。第三天还是继续和大家一起学习 Rust😊

代码缺陷

从代码开发的角度看,一个软件系统想要提供良好用户体验的功能,最基本的要求就是控制缺陷:

  • 语法缺陷 → RLS / Rust Analyzer
  • 类型安全缺陷 → 类型系统
  • 内存和资源安全缺陷 → 所有权、借用检查、生命周期 Rust 通过所有权、借用检查和生命周期检查,来保证内存和资源分配之后,在其生命周期结束之后就会被释放掉
  • 并发安全缺陷 → 所有权、借用检查、生命周期 + 类型系统
  • 错误处理缺陷 → 编译器告警 Rust 使用 Result <T,E> 类型来保证错误的类型安全,还强制要求必须处理这个类型返回的值,避免开发者丢弃错误
  • 代码风格和常见错误引发的缺陷 → cargo fmt / cargo clippy
  • 逻辑缺陷 → 单元测试
  • 功能缺陷 → 集成测试
  • 用户体验缺陷 → 端到端测试 / 手工测试

Rust 准备

安装 Rust

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

开发工具

任何编辑器都可以撰写 Rust 代码,推荐使用 VS Code,因为免费而且速度快。先安装以下插件:

  • rust-analyzer,实时编译和分析你的 Rust 代码,提示代码中的错误
  • rust syntax,为代码提供语法高亮
  • crates,分析当前项目的依赖是否为最新版本
  • better toml,Rust 使用 toml 做项目配置管理,better toml 可以提供语法高亮,提示 toml 文件的错误
  • rust test lens,快速运行某个 Rust 测试
  • Tabnine,基于 AI 的自动补全,可以更快地撰写代码

基本概念

cargo 是 Rust 用來做依賴管理以及开发过程中的任务管理,比如编译、运行、测试、代码格式化等

从一个网页里获取内容并保存成 md 的例子看看 Rust 的整体语法:

// 访问命名空间或者对象的静态函数要使用双冒号 :: 运算符
// 要简化对命名空间内部函数或者数据类型的引用可以使用 use 关键字
use std::fs; 

fn main () { // 可执行体的入口函数是 main() , 函数体用花括号 {} 包裹
    let url = "<https://www.rust-lang.org/>";
    let output = "rust.md"; // 表达式之间用分号 ;分隔

    println!("Fetching url: {}", url);
    // 访问结构体的成员函数或者变量使用 . 运算符
    let body = reqwest::blocking::get(url).unwrap().text().unwrap();

    println!("Convering html to markdown...");
    let md = html2md::parse_html(&body);

    fs::write(output, md.as_bytes()).unwrap();
    println!("Converted markdown has been saved in {}.", output);
}

Rust 的变量默认是不可变的,它符合最小权限原则 (Principle of Least Privilege),帮助我们写出健壮而且正确的代码。 如果要修改变量的值,需要显式使用 mut 关键字

除了 let / static / const / fn 等少数语句外,Rust 绝大多数代码都是表达式 (expression)。所以 if / while / for / loop 都会返回一个值,函数最后一个表达式就是函数的返回值

Rust 支持面向接口编程和泛型编程

Rust 支持类型推导,在编译器能够推导类型的情况下,变量类型一般可以省略,但是常量 (const) 和静态变量 (static) 必须要声明类型

Rust 拥有非常丰富的数据类型和强大的标准库

Rust 还有丰富的控制流程,包括模式匹配

Rust 函数参数的类型和返回值必须要显式定义,如果没有返回值可以省略,返回 uint;函数内部需要提前返回的话,就使用 return 关键字,否则最后一个表达式就是其返回值;如果最后一个表达式后面加了 ;分号,则隐含其返回值为 uint

数据结构

// enum 枚举类型
#[derive(Debug)]
enum Gender {
	Unspecified = 0,
	Female = 1,
	Male = 2,
}

// struct 特殊形式,称为元组结构体,它的域都是匿名,可以用索引访问,适用于简单的结构体
#[derive(Debug, Copy, Clone)]
struct UserId(u64);

// 标准结构体
#[derive(Debug)]
struct User {
	id: UserId,
	gender: Gender,
}

// 定义标签联合体 (tagged union), 定义三种事件
#[derive(Debug)]
enum Event {
	Join(UserId, TopicId)
	Leave(UserId, TopicId)
	Message((UserId, TopicId, string)),
}

一般用 impl 关键字为数据结构实现 trait,Rust 提供了派生宏 (derive macro) ,用来简化标准接口定义,例如 #[derive(Debug)] 为数据结构实现了 Debug trait,提供了 debug 功能,这样就可以通过 {:?} 来用 println! 打印

#[derive(Debug, Copy, Clone)] 里面有 Copy / Clone 两个派生宏,Clone 让数据结构可以被复制,而 Copy 则让数据结构可以在参数传递的时候自动按照字节拷贝

控制流程

顺序执行就是常说的串行执行顺序,也就是一行行代码往下执行

函数调用就是在代码执行过程中,调用了另一个函数,跳入其上下文中执行,直到返回

Rust 支持死循环 loop,条件循环 while,迭代器循环 for。也可以利用 break 提前终止,continue 跳到下一个循环

学习资料

官方的 Rust book ,入门最权威的资料

Rust 死灵书,讲述 Rust 的高级特性

Rust 小练习,巩固知识和概念的理解

标准库文档 Rust 代码的文档系统

微软的 Rust 培训

Rust 语言的备忘单

Rust 网络编程 Rust forum

每星期 Rust 新闻 Rust Top 100

Rust 异步例子 微软大佬 Rust 教程