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);
}