《Rust编程之道》学习笔记二

《Rust编程之道》学习笔记二第2章语言精要好读书,不求甚解;每有会意,便欣然忘食。1Rust语言的基本构成Rust语言主要由以下几个核心部件组成:语言规范编译器核心库标准库包管理器语言规范Rust语言规范主要由Rust语言参考(TheRustRefe

《Rust编程之道》学习笔记二

第 2 章 语言精要

好读书,不求甚解;每有会意,便欣然忘食。

1 Rust 语言的基本构成

Rust 语言主要由以下几个核心部件组成:

  • 语言规范
  • 编译器
  • 核心库
  • 标准库
  • 包管理器

语言规范

Rust 语言规范主要由 Rust 语言参考(The Rust Reference)和 RFC 文档共同构成。

The Rust Reference:<https://doc.rust-lang.org/stable/reference/>

Rust 参考手册 中文版:<https://rustwiki.org/zh-CN/reference/>

Rust 规范文档:https://rustwiki.org/wiki/

Rust 官方文档中文教程:https://rustwiki.org/

编译器

Rust 是一门静态编译型语言。Rust官方的编译器叫 rustc,负责将 Rust 源代码编译为可执行文件或其他库文件(.a、.so、.lib、.dll 等)。

Rustc 有如下特点:

  • rustc 是跨平台的应用程序,支持UNIX/Linux 等类 UNIX 平台,也支持 Windows 平台。
  • rustc 支持交叉编译,可以在当前平台下编译出可运行于其他平台上的应用程序和库。
  • rustc 使用 LLVM 作为编译器后端,具有很好的代码生成和优化技术,支持多个目标平台。
  • rustc 是用 Rust 语言开发的,包含在 Rust 语言源码中。
  • rustc 对 Rust 源码进行词法语法分析、静态类型检查,最终将代码翻译为 LLVM IR。
  • rustc 输出的错误信息非常友好和详尽,是开发者的良师益友。

核心库

Rust 语言的语法由核心库和标准库共同提供。

Rust 核心库是标准库的基础。

可以通过在模块顶部引入 #![no_std] 来使用核心库。

核心库包含如下部分:

  • 基础的Trait
  • 基本原始类型
  • 一些内建宏

标准库

标准库包含的内容大概如下:

  • 一些基本 trait、原始数据类型、功能型数据类型和常用宏等
  • 并发、I/O 和运行时
  • 平台抽象
  • 底层操作接口
  • 可选和错误处理类型 Option 和 Result,以及各种迭代器等

包管理器

把按一定规则组织的多个 rs 文件编译后就得到一个包(crate)。

包是 Rust 代码的基本编译单元,也是程序员直接共享代码的基本单元。

Rust 社区的公开第三方包都集中在 crates.io 网站上面,它们的文档被自动发布到 docs.rs 网站上。

Rust 提供了非常方便的 包管理器 Cargo。

https://doc.rust-lang.org/cargo/

https://rustwiki.org/zh-CN/cargo/index.html

cargo new bin_crate
cargo new --lib lib_crate
  • cargo new 命令默认可以创建一个用于编写可执行二进制文件的项目。

  • cargo new 命令添加 --lib 参数,可以创建用于编写库的项目。

  • cargo build 对项目进行编译

  • cargo run 项目运行

2 语句与表达式

Rust 中的语法可以分成两大类:语句(Statement)和表达式(Expression)。

语句是指要执行的一些操作和产生副作用的表达式。

表达式主要用于计算求值。

语句分为两种:声明语句(Declaration statement)和表达式语句(Expression statement)。

  • 声明语句,用于声明各种语言项(Item),包括声明变量、静态变量、常量、结构体、函数等,以及通过 extern 和 use 关键字引入包和模块等。
  • 表达式语句,特指以分号结尾的表达式。此类表达式求值结果将会被舍弃,并总是返回单元类型 ()。
// extern crate std;
// use std::prelude::v1::*;
fn main() {
  pub fn answer() -> () {
    let a = 40;
    let b = 2;
    assert_eq!(sum(a, b), 42);
  }
  pub fn sum(a: i32, b: i32) -> i32 {
    a + b
  }
  answer();
}

Rust 会为每个 crate 都自动引入标准库模块,除非使用 #![no_std]#[no_std] 属性明确指定了不需要标准库。

关键字 fn 是 function 的缩写。

单元类型拥有唯一的值,就是它本身。

单元类型的概念来自 OCaml,它表示”没有什么特殊的价值“。

函数无返回值的函数默认不需要在函数签名中指定返回类型。

assert_eq! 是宏语句,是Rust提供的断言,允许判断给定的两个表达式求值结果是否相同。

Rust 编译器在解析代码的时候,如果碰到分号,就会继续往后面执行;

如果碰到语句,则执行语句;

如果碰到表达式,则会对表达式求值,如果分号后面什么都没有,就会补上单元值()。

当遇到函数的时候,会将函数体的花括号识别为块表达式(Block Expression)。

块表达式是由一对花括号和一系列表达式组成的,它总是返回块中最后一个表达式的值。

在某种程度上,可以将Rust看作是一切皆表达式。

3 变量与绑定

let 关键字创建变量

let 创建的变量一般称为 绑定(Binding)

它表明了标识符(Identifier)和值(Value)之间建立的一种关联关系。

位置表达式和值表达式

Rust 中的表达式一般可以分为位置表达式(Place Expression)和值表达式(Value Expression)。

在其他语言中,一般叫做左值(LValue)和右值(RValue)

位置表达式是表示内存位置的表达式。分别有以下几类:

  • 本地变量
  • 静态变量
  • 解引用(*expr)
  • 数组索引(expr[expr])
  • 字段引用(expr.field)
  • 位置表达式组合

通过位置表达式可以对某个数据单元的内存进行读写。

除此之外的表达式就是值表达式。

值表达式一般只引用了某个存储单元地址中的数据,它相当于数据值,只能进行读操作。

从语义角度来说,位置表达式代表了持久性数据,值表达式代表了临时数据。

表达式的求值过程在不同的上下文中会有不同的结果。

求值上下文也分为位置上下文(Place Context)和值上下文(Value Context)

位置上下文的表达式:

  • 赋值或者复合赋值语句左侧的操作数
  • 一元引用表达式的独立操作数
  • 包含隐式借用(引用)的操作数
  • math 判别式或let 绑定右侧在使用 ref 模式匹配的时候也是位置上下文。

除了上述几种情况,其余表达式都属于值上下文。

值表达式不能出现在位置上下文中。

pub fn temp() -> i32 {
  return 1;
}
fn main() {
  let x = &temp();
  temp() = *x;  // error
}

temp 函数调用时一个无效的位置表达式,它是值表达式。

不可变绑定与可变绑定

使用 let 关键字声明的位置表达式默认不可变,为不可变绑定。

fn main() {
  let a = 1;
  // a = 2; // immutable and error
  let mut b = 2;
  b = 3; // mutable
}

通过 mut 关键字,可以声明可变的位置表达式,即可变绑定。

可变绑定可以正常修改和赋值。

从语义上来说

let 默认声明的不可变绑定只能对相应的存储单元进行读取

let mut 声明的可变绑定可以对相应的存储单元进行写入

所有权与借用

当位置表达式出现在值上下文中时,该位置表达式将会把内存地址转移给另外一个位置表达式,这其实是所有权的转移。

fn main() {
  let place1 = "hello";
  let place2 = "hello".to_string();
  let other = place1;
  println!("{:?}", place1);
  let other = place2;
  println!("{:?}", place2); // Err: other value used here after move
}

因为 place1 是一个位置表达式,现在出现在了赋值操作符右侧,即一个值上下文内,所以 place1 会将内存地址转移给 other。

在语义上,每个变量绑定实际上都拥有该存储单元的所有权,这种转移内存地址的行为就是所有权(OwnerShip)的转移,在Rust中称为移动(Move)语义,那种不转移的情况实际上是一种复制(Copy)语义。

Rust 没有GC,所以完全依靠所有权来进行内存管理。

Rust 提供借用(Borrow)操作符(&),可以直接获取表达式的存储单元地址,即内存位置。

可以通过该内存位置对存储进行读取。

fn main() {
  let a = [1,2,3];
  let b = &a;
  println!("{:p}", b); // 0x7ffcbc067704
  let mut c = vec![1,2,3];
  let d = &mut c;
  d.push(4);
  println!("{:?}", d); // [1,2,3,4]
  let e = &42;
  assert_eq!(42, *e);
}

通过 println! 宏指定 {:p} 格式,可以打印 b 的指针地址,也就是内存地址。

注意,要获取可变引用,必须先声明可变绑定。

对于字面量 42 来说,其本身属于值表达式。

通过借用操作符,相当于值表达式在位置上下文中进行求值,所以编译器会为 &42 创建一个临时值。

值表达式在位置上下文中求值时会被创建临时值

将“ & ”称为借用操作符,通过借用操作符得到的类型叫引用(Reference)类型。

4 函数与闭包

main 函数代表程序的入口。

函数是通过关键字 fn 定义的。

定义一个 FizzBuzz 函数:输入一个数字,当数字是3的倍数时,输出fizz;当数字是5的倍数时,输出buzz;

当数字是3和5共同的倍数时,输出 fizzbuzz;其他情况返回该数字。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0x750E...B6f5
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。