0x0 前言
本文通过学习
Rust 程序设计语言
记录
在线地址:https://www.rustwiki.org.cn/zh-CN/book/
Rust是一门现代的系统编程语言,具有内存安全、并发性和高性能的特点。
0x1. 搭建Rust环境变量
1.1 安装Rust
下载地址:https://www.rust-lang.org/tools/install
windows: 先搭建好Visual Studio的C++环境,再去安装Rust
ubuntu: curl --proto ‘=https’ --tlsv1.2 -sSf https://sh.rustup.rs | sh && source $HOME/.cargo/env
# ubuntu 安装失败时换一下源
echo "export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static" >> ~/.bashrc
echo "export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup" >> ~/.bashrc
source ~/.bashrc
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 安装完毕后刷新环境变量
source ~/.cargo/env
# Windows设置环境变量
RUSTUP_DIST_SERVER https://rsproxy.cn
RUSTUP_UPDATE_ROOT https://rsproxy.cn/rustup
1.2 VsCode插件配置:
rust-analyzer
:实时编译和分析你的 Rust 代码,提示代码中的错误,并对类型进行标注。
Rust Syntax
:语法高亮。
Crates
:简化Rust和VSCode中的依赖关系管理
Even Better TOML
:Rust 使用 toml 做项目的配置管理,功能齐全的TOML支持。
rust test lens
:快速运行某个 Rust 测试。
Tabnine
:代码补全,注册账号登录即可。
1.3 Clion:下载Rust的插件即可
1.4 替换cargo 镜像源
vscode打开.cargo下的config文件:code $HOME/.cargo/config
[source.crates-io]
# 替换成你目的镜像源
replace-with = 'tuna'
# 清华大学
#注:sparse+ 表示在使用稀疏索引,链接末尾的 / 不能缺少。
#注:$CARGO_HOME:在 Windows 系统默认为:%USERPROFILE%\.cargo,在类 Unix 系统默认为:#$HOME/.cargo
[source.tuna]
registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"
# 中国科学技术大学
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
# 上海交通大学
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"
[net]
git-fetch-with-cli = true
查看版本号:
rustc --version
更新Rust
rustup update
卸载Rust
rustup self uninstall
0x2. Rust Hello Word
fn main() {
println!("Hello, Word!");
}
0x3. Cargo
3.1 创建项目:
cargo new hello_cargo
3.2 编译项目
cargo build
3.3 编译运行项目
cargo run
3.4 cargo check
cargo check
用于检查代码,确保编译通过不产生任何可执行文件,要比cargo build
快的多。
3.5 cargo build --release
-
为发布构建
-
编译时会进行优化
- 代码运行更快,但编译时间更长。
-
会在target下的生成release目录
俩种配置,一个开发,一个正式发布。
3.6 Cargo.toml文件
Cargo.toml
是Rust语言中用于管理项目的配置文件。它位于项目的根目录下,用于指定项目的元数据、依赖关系、构建选项和其他配置信息。
以下是Cargo.toml
文件的一些常见用途:
-
项目元数据: 在
Cargo.toml
中,你可以指定项目的名称、版本、作者、许可证等元数据信息。这些信息对于标识和描述项目非常有用。 -
依赖管理: 你可以列出项目所依赖的外部Crate(Rust包),包括它们的名称、版本和其他信息。当你构建项目时,Cargo会自动下载并管理这些依赖。
-
构建配置: 你可以指定编译选项、环境变量、构建脚本等配置信息,以定制项目的构建过程。
-
测试配置: 你可以在
Cargo.toml
中指定测试文件的位置和其他测试相关的配置。
一个典型的Cargo.toml
文件看起来可能像这样:
[package]
name = "hello_cargo"
version = "0.1.0"
authors = ["Your Name <your.email@example.com>"]
edition = "2021"
[dependencies]
serde = "1.0"
rand = "0.7"
在这个例子中,[package]
部分包含了项目的元数据,包括名称、版本、作者等。[dependencies]
部分列出了项目所依赖的外部Crate:rand
。当你运行cargo build
命令时,Cargo会根据Cargo.toml
文件中的信息下载并构建这些依赖。
Cargo.toml
是Rust项目的重要配置文件,用于管理项目的元数据和依赖关系。通过编辑Cargo.toml
,你可以方便地配置和管理你的Rust项目。
3.7 Cargo.lock文件
cargo.lock
文件是Rust中Cargo构建系统自动生成的文件,用于锁定项目依赖的确切版本。它的作用包括:
-
确保依赖版本一致性:
cargo.lock
文件记录了项目当前使用的每个依赖包的确切版本号。这样可以确保在后续构建时使用相同的依赖版本,从而保证构建的一致性。 -
加速构建过程: 当项目已经有了
cargo.lock
文件,Cargo会直接使用其中记录的依赖版本,而不会重新解析依赖关系。这可以加快构建过程,因为不需要再次下载和解析依赖。 -
协作开发时的一致性: 如果项目是由多个开发者协作开发的,
cargo.lock
文件可以确保每个人使用相同的依赖版本,避免因为依赖版本不一致导致的问题。 -
版本管理:
cargo.lock
文件可以被纳入版本控制系统(如Git),这样可以确保项目的依赖版本在不同的开发环境中保持一致。
3.8 Cargo update
- 检查最新版本: Cargo会检查每个依赖包的最新版本,并将这些信息记录在
Cargo.lock
文件中。 - 更新锁定文件: 如果有新版本的依赖包可用,Cargo会更新
Cargo.lock
文件中记录的依赖版本信息。这样可以确保后续的构建过程会使用最新的兼容版本。 - 不更新
Cargo.toml
文件: 值得注意的是,cargo update
命令不会修改Cargo.toml
文件中列出的依赖版本信息。它只会更新Cargo.lock
文件中的依赖版本信息。
0x4.基础语法
4.1 变量、占位符、输入输出
use std::io;// use声明,它将std::io模块引入当前作用域。std::io模块包含处理输入/输出功能的函数和类型。
use rand::Rng;
use std::cmp::Ordering;
fn main() {
let random_number = rand::thread_rng().gen_range(1..=10);
println!("You random_number : {}", random_number);
loop {
//println!是一个宏,它将字符串输出到标准输出
println!("please input value.");
// 初始化为一个新的空String对象。
// String是Rust标准库提供的一个动态、可增长的字符串类型。
// mut关键字表示guess变量的值可以被改变,即它是可变的。
let mut input = String::new();
// 这是一个多行表达式,它使用了std::io模块的stdin函数来获取标准输入的句柄。
// 然后调用read_line方法尝试从标准输入读取一行数据到input变量中。
// read_line方法需要一个可变引用作为参数,所以传递&mut input。
// read_line方法返回一个Result类型,这是一个枚举,表示操作可能成功(Ok)也可能失败(Err)。
// 如果出现错误,比如无法从标准输入读取数据,expect方法会被调用。
// expect方法会导致程序崩溃并打印出传递给它的消息,
// 如果它接收到的Result值是Err的话,程序将会崩溃并打印出"Failed to read line"。
io::stdin()
.read_line(&mut input)//返回err或ok
.expect("read fail");
// trim() 去除用户输入字符串两端的空白字符
// 按 enter 键会得到一个回车和一个换行符 \r\n,trim 方法会消除\n 或\r\n,只留下 输入的内容。
// parse() 字符串解析为其他类型的值,包括数字、布尔值、甚至自定义类型
let input: u32 = match input.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("the input is not a numbr!");
continue;
}
};
// let input: u32 = input.trim().parse().expect("the input is not a numbr!");
println!("You input value: {}", input);
match input.cmp(&random_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
let
声明变量,并且默认的变量是不可变的
, 加上mut
声明可变变量
{}
花括号是占位符use
声明,它将std::io模块引入当前作用域,类似头文件println!
是一个宏,它将字符串输出到标准输出stdin
函数来获取标准输入的句柄
4.2 常量
在 Rust 中,常量和不可变变量(使用 let
声明的变量)有不同的作用和使用场景。下面是一个简单的代码示例来说明它们的区别以及为什么需要同时存在常量和不可变变量:
const PI: f64 = 3.14159; // 声明一个常量 PI,表示圆周率-在编译时确定的值
fn main() {
// 使用 let 声明一个不可变变量 radius,表示圆的半径-在程序运行时需要保持不变的值
let radius: i32 = getRadius();
let area = PI * (radius * radius) as f64; // 计算圆的面积
println!("The area of the circle is: {}", area);
}
- 在编译时确定的值:这些值在编译时就可以确定,因此被称为编译时常量。编译时常量可以是字面量、由其他编译时常量计算得出的值,或者某些函数的返回值。在 Rust 中,常量和静态变量(
static
)可以用来表示编译时确定的值。 - 在程序运行时需要保持不变的值:这些值在程序运行时确定,但是在确定后需要保持不变。这些值通常是由用户输入、配置文件读取或者程序运行时计算得出的。在 Rust 中,使用
let
关键字声明的变量默认是不可变的,因此可以用来表示在程序运行时需要保持不变的值。
4.3 shadowing(遮蔽)
允许在同一作用域内多次使用相同的变量名,并且可以改变变量的类型或值。
// shadowing
let x = 5; // 第一次声明变量 x
println!("The value of x is: {}", x); // 输出 5
let x = x + 5; // 重新声明变量 x,遮蔽了前面的声明
println!("The value of x is: {}", x); // 输出 10
let x = "hello";
4.4 随机数
//Cargo.toml添加依赖
[dependencies]
rand = "0.8.5"
/**
* 方法原型
* 泛型方法,它接受一个范围range作为参数,并返回类型为T的随机数。
* 泛型参数T表示随机数的类型,而泛型参数R表示范围的类型。
* &mut self是一个方法参数的类型标识,表示这个方法是一个实例方法可以在实例上调用,并且可以修改实例的内容。
* Range<T>表示范围的类型,T是范围的元素类型。
*/
fn gen_range<T, R>(&mut self, range: R) -> T
where
T: SampleUniform,
R: SampleRange<T>
{
assert!(!range.is_empty(), "cannot sample empty range");
range.sample_single(self)
}
在Rust中,方法的第一个参数通常是
self
,用来表示方法的调用者。&mut self
表示这个方法需要一个可变引用作为调用者,也就是说,这个方法可以修改调用者的内容。这种方式允许方法修改调用者的状态,因此在方法中可以对调用者进行修改操作。因此,
&mut self
表示一个可变引用,允许方法修改调用者的内容。
use std::io;
use rand::Rng;
fn main() {
// 生成1到10之间的随机数,包括1和10
let random_number = rand::thread_rng().gen_range(1..=10);
println!("You random_number : {}", random_number);
}
4.5 条件控制语句
if condition {
// code
} else if another_condition {
// code
} else {
// code
}
4.6 match表达式
match
表达式是一种用于模式匹配的语法结构。它可以用于匹配一个值的不同情况,并执行相应的代码块。match
表达式通常用于处理枚举类型、引用和其他复杂数据结构。
fn main() {
let number = 3;
//`_`是通配符,表示匹配所有未明确指定的情况。
match number {
1 => println!("It is one"),
2 => println!("It is two"),
3 => println!("It is three"),
_ => println!("It is something else"),
}
}
4.7 循环
-
loop
循环:loop
关键字用于创建一个无限循环,它会一直重复执行循环体中的代码,直到遇到显式的break
语句才会停止。loop
循环通常用于需要不断执行直到满足某个条件的情况。示例:
loop { println!("This will loop forever!"); break; // 通过 break 语句来退出循环 }
-
while
循环:while
关键字用于创建一个基于条件的循环,它会在条件为真时重复执行循环体中的代码,直到条件为假才会停止。示例:
let mut count = 0; while count < 5 { println!("Count: {}", count); count += 1; }
-
for
循环:for
关键字用于迭代集合中的元素,它会遍历集合中的每个元素,并执行循环体中的代码。示例:
let numbers = [1, 2, 3, 4, 5]; for number in numbers.iter() { println!("Number: {}", number); }
4.8 数据类型
rust 是一种静态类型的语言,它必须在编译期知道所有变量的类型
编译器通常可以根据值和使用方式推导出我们想要使用的类型。
当出现let input: u32 = input.trim().parse().expect("the input is not a numbr!");
将String
转换成数值类型时,我们必须加上一个类型标注,反之编译报错。
4.8.1 标量类型-一个标量类型代表一个单个的值
标量包含:整型、浮点型、布尔型和字符
整形
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 -128 到 127 | u8 0 到 255 |
16 位 | i16 | u16 |
32 位 | i32 | u32 |
64 位 | i64 | u64 |
128 位 | i128 | u128 |
arch | isize | usize |
isize
和 usize
类型取决于程序运行的计算机体系结构,在表中表示为“arch”:若使用 64 位架构系统则为 64 位,若使用 32 位架构系统则为 32 位。
//可能属于多种数字类型的数字字面量允许使用类型后缀来指定类型,x , y , z都属于无符号8位整型
let x = 10u8;
let y = 5u8;
let z = x + y;//15
print!("z is {}", z);
// 数字字面量还可以使用 _ 作为可视分隔符以方便读数,如 1000_000u32
let x = 1000_000u32;
print!("x is {}", x);//1000000
- 十进制:没有前缀的数字(例如
123
)表示十进制整数。 - 十六进制:以
0x
或0X
开头的数字表示十六进制整数(例如0xFF
)。 - 八进制:以
0o
或0O
开头的数字表示八进制整数(例如0o77
)。 - 二进制:以
0b
或0B
开头的数字表示二进制整数(例如0b1010
)。
浮点数
Rust 的浮点型是 f32
和 f64
,它们的大小分别为 32 位和 64 位。默认浮点类型是 f64
,所有浮点型都是有符号的。
let x = 2.0; // f64
let y: f32 = 3.0; // f32
布尔类型
let f: bool = false;
字符类型
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
4.8.2 复合类型-一个复合类型代表一组值
复合类型可以将多个值组合成一个类型。Rust 有两种基本的复合类型:元组(tuple)和数组(array)。
Tuple
- 用小括号声明
- 每个位置都对应一个类型,内部元素不必相同
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, _y, _z) = tup;
println!("The value of x is: {}", x);
println!("The value of y is: {}", tup.1);
println!("The value of z is: {}", tup.2);
没有任何值的元组 () 是一种特殊的类型,只有一个值,也写成 ()称为单元值(unit value)。如果表达式不返回任何其他值,就隐式地返回单元值。
在 Rust 中,使用下划线
_
开头的变量名表示一个未使用的变量。这是一种常见的约定,用于告诉编译器和其他开发者,这个变量是有意未使用的,以避免产生未使用变量的警告。
Array
数组的每个元素必须具有相同的类型,Rust 中的数组具有固定长度,数据分配到栈(stack)。
let a: [i32; 5] = [1, 2, 3, 4, 5];
let same_array = [3;5] //等价于let sameArray = [3,3,3,3,3];
//如果数组越界会抛出下面异常
//index out of bounds: the len is 5 but the index is 5
函数
Rust 代码中的函数和变量名使用下划线命名法(snake case,直译为蛇形命名法)规范风格。在下划线命名法中,所有字母都是小写并使用下划线分隔单词。
//默认最后一行语句为返回值且不能加';'分号,其他场景使用return
fn add(x: i32, y: i32) -> i32{
x + y
}