Rust 学习(开篇)
此刻,就从 Rust 开始挑战一下自己,挑战一下那些一直学不下去的东西。
本想写一些学习笔记,但是发现已经有这么好的教程了,https://github.com/sunface/rust-course 。所以下面的内容,大家也可以不用看了,哈哈哈,就当是我的学习开篇。
环境搭建
安装 Rust
无论使用何种系统, 均可以根据 Rust 官方网站提供的 rustup-init 工具完成 Rust 的安装. rustup-init 下载地址:
https://www.rust-lang.org/zh-CN/tools/install.
网站会自动识别你的操作系统并给出提示, 遵循网站提示一步一步执行即可.
当安装完成后, 可使用以下命令进行测试, 如果正确输出版本号则表明安装已经成功.
$ rustc --version
提示: 如果你使用的是 Linux 系统, 那么在 rustup-init 运行完成后它会在命令行中提示你将软件安装目录加入 PATH 环境变量中. 对于 Windows 系统来说不需要这一步.
开发工具
使用 vscode, 并配置 rust-analyzer 插件。
- 安装 vscode
- 在 vscode 的插件市场中, 安装
rust-analyzer插件,rust-analyzer包含代码提示, 代码检查, 自动补全等多种功能.
Hello World
Cargo 是 Rust 默认的项目管理工具, 它几乎会贯穿你的整个 Rust 开发周期, 包括项目的创建, 引入第三方库, 编译, 测试和运行等. 使用如下命令可以创建一个新的 Rust 项目:
$ cargo new hello
Cargo 默认会携带 --bin 参数, 这意味着该项目是一个二进制程序. 如果要创建一个库, 我们需要传递 --lib.
生成的项目目录结构如下:
.
├── Cargo.toml
└── src
└── main.rs
- Cargo.toml 是项目的描述文件, 它里面保存了项目的依赖库, 项目的名称, 版本号等信息
- src 是源码目录
- src/main.rs 是项目的入口点
使用如下命令可以编译并运行项目:
$ cargo run
Hello World!
Cargo
Cargo 里面有许多有用的命令, 一些常用的命令包括:
cargo new生成新的项目模板cargo build构建项目, 生成可执行文件或依赖cargo run构建并运行项目cargo test运行测试用例cargo check检查项目代码, 由于 Rust 编译较慢, 因此在开发中常用 check 代替 build 命令cargo doc生成项目文档cargo publish将库发布到 crates.io
除了以上 cargo 自带的命令外, cargo 还支持安装额外的扩展命令, 例如格式化工具. rustfmt 是一个可以自定义风格的 rust 代码格式化工具, 使用如下命令安装它:
$ rustup component add rustfmt
在项目根目录输入以下命令, 会自动格式化项目内的全部 Rust 源文件.
$ cargo fmt
数据类型
Rust 是静态强类型语言.
- 静态类型: 在编译期对类型进行检查
- 动态类型: 在运行期对类型进行检查
- 强类型: 不允许隐式类型转换
- 弱类型: 允许进行隐式类型转
变量和可变性
创建和使用变量
在 Rust 代码中, 可以使用 let 关键字将值绑定到变量.
fn main() {
let x = 5;
println!("The value of x is: {}", x);
}
println 是一个宏, 它是最常用的将数据打印在屏幕上的方法. 目前, 我们可以简单地将它视为一个拥有可变参数数量的函数, 在后面的章节中我们会对宏进行详细的讨论.
可变性
在 Rust 中, 变量默认是不可变的, 一旦一个值绑定到一个名称, 就不能更改该值.
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6; // cannot assign twice to immutable variable `x`
println!("The value of x is: {}", x);
}
但有时候允许变量可变是非常有用的. 通过在变量名前面添加 mut 来使它们可变.
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
常量和变量
不可变变量容易让你联想到另一个概念: 常量. 在 Rust 中, 常量使用 const 定义, 而变量使用 let 定义.
- 不允许对常量使用修饰词 mut, 常量始终是不可变的
- 必须显示标注常量的类型
- 常量可以在任何作用域中声明, 包括全局作用域
- 常量只能设置为常量表达式, 而不能设置为函数调用的结果或只能在运行时计算的任何其他值.
const A_CONST: i32 = 1;
常量和不可变变量的概念有时候可能比较容易搞混,最大的区别在于:常量是在编译期间进行求值的,而不可变变量实在运行期间进行求值的。如下面这样:
fn main() {
// 这是正确的
const A_COUNT: i32 = 1 + 2 + 3;
fn get_number() -> i32 {
42
}
// 这是错误的,函数是在运行期间执行的,所以不能赋给常量
const B_COUNT: i32 = get_number();
// 这是正确的,不可变变量是在运行期间求值的
let b = get_number();
}
隐藏(Shadowing)
可以声明一个与前一个变量同名的新变量, 并且新变量会隐藏前一个变量, 这种操作被成为隐藏(Shadowing).
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
// The value of x is: 12
}
基本类型
Rust 是一门静态编程语言, 所有变量的类型必须在编译期就被明确固定.
整数
Rust 中有 12 种不同的整数类型
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
- 对于未明确标注类型的整数, Rust 默认采用
i32. isize和usize根据系统的不同而有不同的长度.
浮点数
Rust 有两种浮点数类型, 为 f32 和 f64, 后者精度更高.
对于未明确标注类型的小数, Rust 默认采用 f64.
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
布尔值
与大多数其他编程语言一样, Rust 中的布尔类型有两个可能的值: true 和 false. 布尔值的大小是一个字节.
fn main() {
let t = true;
let f: bool = false;
}
字符
Rust 支持单个字符. 字符使用单引号包装.
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
}
复合类型
元组
元组是将多个具有各种类型的值组合成一个复合类型的通用方法. 元组有固定的长度: 一旦声明, 它们的大小就不能增长或收缩.
我们通过在括号内写一个逗号分隔的值列表来创建一个元组. 元组中的每个位置都有一个类型, 元组中不同值的类型不必相同.
fn main() {
let a: i32 = 10;
let b: char = 'A';
// 创建一个元组
let mytuple: (i32, char) = (a, b);
// 从元组中读取一个值
println!(".0={:?}", mytuple.0);
println!(".1={:?}", mytuple.1);
// 解封装
let (c, d) = mytuple;
println!("c={} d={}", c, d);
}
数组
另一种拥有多个数据集合的方法是使用数组. 与元组不同, 数组中的每个元素都必须具有相同的类型. Rust 中的数组不同于其他一些语言中的数组, Rust 中的数组具有固定长度.
数组下标以 0 开始, 同时 Rust 存在越界检查.
fn main() {
// 创建数组, [i32; 3] 是数组的类型提示, 表示元素的类型是 i32, 共有 3 个元素
let myarray: [i32; 3] = [1, 2, 3];
// 根据索引获取一个值, 数组下标从 0 开始
println!("{:?}", myarray[1]);
// 索引不能越界
println!("{:?}", myarray[3]);
// 如果数组的每个元素都有相同的值, 我们还可以简化数组的初始化
let myarray: [i32; 3] = [0; 3];
println!("{:?}", myarray[1]);
}
切片类型
切片类型是对一个数组(包括固定大小数组和动态数组)的引用片段, 有利于安全有效地访问数组的一部分, 而不需要拷贝数组或数组中的内容. 切片在编译的时候其长度是未知的, 在底层实现上, 一个切片保存着两个 uszie 成员, 第一个 usize 成员指向切片起始位置的指针, 第二个 usize 成员表示切片长度.
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice = &arr[0..3]; // 取前 3 个元素
println!("slice[0]={}, len={}", slice[0], slice.len());
}
字符串
直接参见:https://course.rs/basic/compound-type/string-slice.html
结构体
结构体是多种不同数据类型的组合. 它与元组类似, 但区别在于我们可以为每个成员命名. 可以使用 struct 关键字创建三种类型的结构:
- 元组结构
- 经典的 C 结构
- 无字段的单元结构(通常在泛型里使用较多)
结构体使用驼峰命名.
// 元组结构
struct Pair(i32, f32);
// 经典的 C 结构
struct Person {
name: String,
age: u8,
}
// 无字段的单元结构, 在泛型中较为常用
struct Unit;
fn main() {
// 结构体的实例化
let pair = Pair(10, 4.2);
let person = Persion {
name: String::from("jack"),
age: 21,
};
let unit = Unit;
// 从结构体中获取成员
println!("{}", pari.0);
println!("{}", persion.name);
}