rust学习笔记(1):Day1 Moring
下面笔记都是使用Windows系统
环境配置
首先需要安装Rustup和mingw-w64,其中Rustup用于安装Rust编译器和Cargo,编译出可执行文件还需要mingw-w64进行连接,当然如果你的电脑有装VS而且装msvc编译工具,也可以不使用mingw。
安装Rustup
如果你的系统已经安装了Chocolatey 或者 Scoop,可以使用他们安装Rustup,我就为了方便就用系统自带的Winget来安装了:
1  | winget install rustup  | 
然后安装工具链的话,如果你的电脑有msvc,就下使用msvc的工具链:
1  | rustup toolchain install stable-x86_64-pc-windows-msvc  | 
如果你的电脑没有msvc,推荐使用mingw,更加小巧而且安装方便:
1  | rustup toolchain install stable-x86_64-pc-windows-gnu  | 
工具链也可以安装多个,可以用下面的方法切换:
1  | # 切到mingw  | 
安装Mingw
如果你的电脑没有msvc,可以选择安装mingw,当然你要装msvc也可以,但是因为我是使用mingw所以下面教程都是使用mingw。
打开mingw的github release页面,选择最新的版本,比如写这篇笔记时最新是12.2.0,那么我们可以点击Assets下面的:x86_64-12.2.0-release-posix-seh-msvcrt-rt_v10-rev2.7z 进行下载。
下载完成后我们可以找个位置存放,比如我放到了:E:\Program Files\mingw64
然后打开环境变量配置,在Path加入:E:\Program Files\mingw64\bin 别漏掉最后面的bin目录哦
创建Rust项目
我使用的是Clion加Rust插件,当然如果没有买jetbrains产品的话也可以使用Idea社区版然后安装rust插件。
安装完Rust插件之后,新建项目就会有Rust选项,选择工具链位置,如果已经弹出来那就可以不用自己手选,如果是没有的话,应该是在:C:\Users\你的用户名\.cargo\bin
下面有个标准库不知道有没有用,反正我点了下面的下载按钮之后就弹出来了。
创建完成后项目有一个main.rs:
1  | fn main() {  | 
点击运行成功打印:Hello, world!
Cargo使用
如果在终端运行的话,只需要执行:cargo run
如果需要构建可执行文件只需要:cargo build,如果需要优化可执行文件就加上选项:cargo build --release
Hello World
首先看看上面提到的代码:
1  | fn main() {  | 
- 函数通过fn引入。
 - 像在C和c++中一样,块由花括号分隔。
 - main函数是程序的入口点。
 - Rust有卫生宏,
println!就是一个例子。 - Rust字符串是UTF-8编码的,可以包含任何Unicode字符。
 
简单示例
1  | fn main() { // 程序入口  | 
为什么使用Rust
- 编译时内存安全。
 - 缺乏未定义的运行时行为。
 - 现代语言特征。
 
编译时保证
编译时的静态内存管理:
- 没有未初始化的变量。
 - 基本上没有内存泄漏。
 - 没有double-frees。
 - 没有use-after-free。
 - 没有空指针。
 - 没有被遗忘的锁定互斥对象。
 - 线程之间没有数据争用。
 - 没有迭代器失效。
 
从技术上讲,在(安全的)Rust中产生内存泄漏是可能的。Box::leak方法允许从Box中获取原始引用,然后在不运行析构函数的情况下删除Box。这可以用于获得运行时初始化和运行时大小的静态变量。或者简单地说,std::mem::forget函数,它使编译器忘记一个值,这意味着析构函数永远不会运行。在安全的Rust中有许多其他的方法来创建泄漏,但是为了本课程的目的,没有内存泄漏应该被理解为几乎没有意外的内存泄漏。
运行时保证
运行时没有未定义的行为:
- 数组访问是边界检查。
 - 定义了整数溢出。
 
现代特性
Rust是在过去40年里积累的经验。
语言特性
- 枚举和模式匹配。
 - 泛型。
 - 没有开销的FFI。
 
工具
- 非常良好的编译器错误提示。
 - 内置依赖管理器。
 - 内置测试支持。
 
基础语法
Rust的很多语法对您来说都是熟悉的C或c++语法:
- 块和范围由花括号分隔。
 - 行注释使用 
//开始,块注释使用:/* ... */ - 关键字如if和while的作用相同。
 - 变量赋值用
=,比较用==。 
基础类型
| 分类 | 类型 | 字面量 | 
|---|---|---|
| 带符号整数 | i8, i16, i32, i64, i128, isize | -10, 0, 1_000, 123i64 | 
| 无符号整数 | u8, u16, u32, u64, u128, usize | 0, 123, 10u16 | 
| 浮点数 | f32, f64 | 3.14, -10.0e20, 2f32 | 
| 字符串 | &str | “foo”, r#"\\"# | 
| Unicode标量值 | char | ‘a’, ‘α’, ‘∞’ | 
| 字节字符串 | &[u8] | 
b”abc”, br#" " "# | 
| 布尔值 | bool | true, false | 
类型的宽度如下所示:
- iN、uN和fN的宽度为N位
 - isize 和 usize 是指针的宽度
 - char 是 32 位宽
 - bool 是 8 位宽
 
复合类型
| 分类 | 类型 | 字面量 | 
|---|---|---|
| 数组 | [T; N] | [20, 30, 40], [0; 3] | 
| 元组 | (T1, T2, T3, …) | (‘x’, 1.2, 0) | 
数组分配和访问:
1  | fn main() {  | 
元组分配和访问:
1  | fn main() {  | 
引用
类似C++,Rust这样引用:
1  | fn main() {  | 
和C++不一样的地方:
- 在给
ref_x赋值时,我们必须解引用它,类似于C指针 - Rust在某些情况下会自动解引用,特别是在调用方法时(尝试
count_ones)。 
悬空引用
Rust将静态禁止悬空引用
1  | fn main() {  | 
- 引用被称为借用它所引用的值。
 - Rust正在跟踪所有引用的生命周期,以确保它们的生存时间足够长。
 - 当我们讲到所有权时,我们会更多地讨论借用。
 
切片/Slices
切片为您提供了一个更大集合的视图
1  | fn main() {  | 
- 切片从切片类型中借用数据
 - 提问:如果修改
a[3]会怎样 
String vs str
现在我们可以这样理解Rust中的两种字符串类型:
1  | fn main() {  | 
Rust 术语:
&str是对字符串片的不可变引用。String是可变字符串缓冲区。
函数/Functions
Rust版的著名的FizzBuzz面试问题:
1  | fn main() {  | 
方法/Methods
Rust有方法,它们只是与特定类型相关联的函数。方法的第一个参数是与其关联的类型的实例:
1  | struct Rectangle {  | 
函数重载
Rust不支持重载:
- 每个函数都只有一个实现:
- 总是接受固定数量的参数。
 - 始终接受一组参数类型。
 
 - 不支持默认值:
- 所有调用的地方的参数个数相同。
 - 宏有时被用作一种替代方法。
 
 
然而,函数参数可以是通用的:
1  | fn pick_one<T>(a: T, b: T) -> T {  | 
练习1
在这些练习中,我们将探索Rust的两个部分:
- 类型之间的隐式转换。
 - 数组和 for 循环。
 
隐式转型
Rust不会自动进行类型之间的隐式转换(与c++不同)。你可以在这样的程序中看到这一点:
1  | fn multiply(x: i16, y: i16) -> i16 {  | 
Rust整数类型都实现了From<T>和Into<T>traits(翻译:特性),以便我们在它们之间进行转换。From<T> trait有一个From()方法,类似地,Into<T> trait有一个Into()方法。实现这些特征是一种类型表示它可以转换为另一种类型的方式。
标准库有一个用于i16的From<i8>实现,这意味着我们可以通过调用i16::from(x)将类型为i8的变量x转换为i16。或者,更简单一点,使用x.into(),因为i16实现的From<i8>会自动为i8创建Into<i16>的实现。
- 执行上面的程序并查看编译器错误。(报错我在上面标出来了)
 - 更新上面的代码以使用
into()进行转换。 - 将x和y的类型更改为其他类型(例如f32, bool, i128),以查看哪些类型可以转换为其他类型。尝试将小类型转换为大类型,或者反过来转换。检查标准库文档,查看是否为您检查的对实现了
From<T>。 
数组和for循环
我们看到数组可以这样声明:
1  | let array = [10, 20, 30];  | 
你可以通过 {:?} 来打印数组:
1  | fn main() {  | 
Rust允许您使用for关键字迭代数组和范围等内容:
1  | fn main() {  | 
使用上面的方法来写一个函数pretty_print,它可以漂亮地打印一个矩阵,以及一个函数transpose,它将转置一个矩阵(将行转换为列)。
1  | | 1 2 3 | | 1 4 7 |  | 
硬编码两个函数操作3x3矩阵。
复制下面的代码到 https://play.rust-lang.org/ 并且实现功能
1  | // TODO: 在完成实现后删除它。  | 
这是我的作业答案,聪明的小伙伴可以看看有没有值得改进的地方:
1  | fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {  | 
附加题
你可以使用&[i32]片代替硬编码的 3 x 3 矩阵作为参数和返回类型吗?对于二维片中的片,类似于&[&[i32]]。为什么或为什么不?
有关生产质量实现,请参阅ndarray crate。