Rust Learning basic

RUST Basic

Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)

Install

rustup 是一个管理 Rust 版本和相关工具的命令行工具

rust_install
rust_install

环境变量

rust_env
rust_env

更新 $rustup update

安装状态 $rustc --version 输出 rustc 1.67.1 (d5a82bbd2 2023-02-07)

查看文档 rustup doc会自动使用默认浏览器打开安装的离线文档页面

Basic

  • 缩进使用4个空格,而不是一个tab
  • 调用的宏时,名字后有!,例如println!("hi human");
  • rust中的模块被称为crates
  • 使用snake case编程风格,所有字母小写并使用下划线分隔单词

编译

rust和c++一样是预编译静态类型语言

rustc .\main.rs

Cargo

Cargo是rust的构建系统和包管理器,可以自动下载依赖库,在使用rustup安装时一并安装到系统中。

创建一个项目执行

$cargo new cargo_demo

会自动创建一个src目录,一个.gitignore文件和Cargo.toml文件

Cargo使用TOML (Tom’s Obvious, Minimal Language) 格式作为项目配置文件

[package]以[]开始的是一个片段

  • 编译工程 在工程目录下执行cargo build,编译时间很长,生成的文件在target的debug目录下
  • cargo run编译并直接运行
  • cargo check代码检查
  • cargo build --release编译release版本
依赖

在Cargo.toml的[dependencies]添加依赖库crate,添加一个生成随机数的rand库,版本为0.8.5

1
2
3
4
5
6
7
[package]
name = "cargo_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.8.5"

再次执行build后,会下载所有依赖的库,包括rand依赖的库

Cargo.lock

工程中的Cargo.lock文件记录了第一次构建时,所有符合要求的依赖库版本,以后再次构建不会再去找依赖库的版本,方便今后“可重复构建”

如果没有修改工程配置,使用cargo update可以强制更新当前配置文件设置的最新库版本,例如更新到配置文件中指定的最新版本

如果修改了toml的配置文件,执行build时,就会下载最新的库文件。

依赖库离线打包

在工程设置好cargo.toml文件后,在工程的根目录执行cargo vendor,可以把当前工程的依赖库下载到工程根目录下的vendor目录中。

在工程的根目录中新建.cargo目录,并在其中新建config配置文件,配置以下内容让工程使用指定目录的依赖库程序

1
2
3
4
5
[source.crates-io]
replace-with = "vendored-sources"

[source.vendored-sources]
directory = "vendor"

把当前工程的整个目录拷贝到其他不能联网的机器,就会使用下载好的依赖库文件,同时也可以提高编译效率,不用每次都重新下载依赖库了。

文档

执行cargo doc --open可以构建本地依赖库的文档,并在浏览器中打开

cargo_doc
cargo_doc

示例程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
println!("Guess my age!");

let my_age = rand::thread_rng().gen_range(1..=100);
loop {
println!("Input your guess: {my_age}");

let mut guess = String::new(); // mut 可变变量
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue, // -是一个通配符,匹配所有Err值,如果不能转换为数字,进入下次循环
};

println!("You guessed: {guess}"); // {}占位符,可以打印变量或表达式结果

match guess.cmp(&my_age) {
Ordering::Less => println!("Small"),
Ordering::Greater => println!("Big"),
Ordering::Equal => {
println!("Right");
break;
}
}
}
}

基本语法

变量

变量默认是不可改变的immutable,一旦一个值绑定到了一个变量上,就不能改变这个变量的值。

如果修改一个不可变变量的值,会有这个错误:error[E0384]: cannot assign twice to immutable variable game
不可变变量的好处:

  • 并发程序在编译时避免多线程问题?

定义可变变量需要使用mut关键字,虽然可以修改变量的值,但是不能更改变量的数据类型

1
let mut game = "cod";

常量

常量是固定不可变的,使用const关键字,常量可以在任何作用域声明,必须是表达式,不能在运行时计算出值。

1
const SECONDS_OF_DAY: u32 = 24*60*60;

隐藏(shadowing)

可以定义一个和之前变量同名的新变量,前一个变量会被隐藏,当第二个变量退出自己的作用域后,变量会恢复第一个变量的值。隐藏是新建了一个变量,并不是改变原来变量的值,和mut完全不同。

1
2
3
4
5
6
let game = "cod";
{
let game = "halo";
println!("The best FPS is {game}"); //halo
}
println!("The best FPS is {game}"); // cod

数据类型

标量(scalar)

表示单独的一个数值

  • 整型:u8, i8(-128~127), u16, i16, u128, i128, usize, isize和程序架构绑定。变量赋值时,可以使用数据类型来指定类型,例如56u8指定数据类型为u8,数字之间可以使用下划线_分隔方便读数,如5_600表示5600.
  • 数字类型表示:十六进制(hex) 0xFF; 八进制(Octal) 0o77; 二进制(binary) 0b1111_0000; 字节(仅能用于u8) b’A’
  • 整数溢出:例如给一个u8类型变量赋值256时,debug版本会出现panic错误,release版本会给变量赋值为 0,257赋值为1进行回绕。标准库提供了检查溢出的方法例如overflowing_*
  • 浮点型:f32, f64,默认为f64。使用IEEE-754标准
  • 布尔型:bool 两个值truefalse
  • 字符类型:char 占4个字节,代表一个Unicode标量值。范围U+0000~U+7DFFU+E000~U+10FFFF在内的值。
复合类型(Compound types)

将多个值组合成一个类型

元组类型

元组长度固定,一旦声明,长度不能改变。元组中的每一个位置的数据类型可以是不同的。可以使用模式匹配来解构(destructure)元组值。也可以使用元组变量名加.索引的方式获取值。

1
2
3
4
5
let tup: (i32, f64, u8) = (500, 3.6, 1);
let (x, y, z) = tup; // destructuring
let x = tup.0;
println!("The value of x is : {x}");
println!("The value of y is : {y}");

没有任何值的元组称作单元(unit),表示空值或空的返回类型。

数组类型

数组中每个元素的数据类型相同,且长度固定。

1
2
3
4
let food = ["breakfast", "lunch", "supper"];
let data:[i32; 3] = [1, 2, 3];
let data = [6, 3]; // [6, 6, 6]
let num = data[0];

函数

函数声明使用fn关键字开始,每个参数必须声明类型,在函数参数列表后使用->指明函数的返回类型

1
2
3
4
5
6
7
fn cal_price(val: f64, fac: f64) -> f64  {
let price = val*fac;
println!("The deal price is {price}");
price // return a expression as return value
}

let price = cal_price(21.5, 1.25);

语句(statements)是执行一些操作但不返回值的指令

表达式(Expressions)计算并产生一个值,表达式结尾没有分号

控制流

条件表达式

if后跟一个条件,和其他语言类似,这个条件必须返回bool类型的值。if表达式可以给let赋值。

1
2
3
4
5
6
7
8
9
10
11
   let number = 255;
if number > 255 {
println!("greater than 255");
} else if number == 0 {
println!("nonsense");
} else {
println!("less than 255 except 0");
}
// 这种情况下的所有分支返回的数据类型必须相同,否则编译器无法确定num的类型
// 每一个分支中都是一个表达式,数字后面没有分号结束。
let num = if number > 50 { 100 } else { 0 };
循环
loop

无条件的循环执行,除非执行了break或程序中断。可以在loop循环的break语句中返回值。

1
2
3
4
5
6
7
8
let mut counter = 0;
let result = loop {
counter += 1;
if counter >= 10 {
break counter * 5;
}
};
println!("The last counter is {result}");
循环标签

循环标签可以给一个循环指定一个名字,默认情况下break和continue作用于此时最内层的循环,使用标签可以让他们作用于指定的循环。标签使用单引号作为开始.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let mut counter = 0;
'count_up: loop {
counter += 1;
println!("counter = {counter}");

let mut remain = 10;

loop {
println!("remain = {remain}");
if remain < 5 {
break; // 只跳出remain的循环
}
if counter == 10 {
break 'count_up; // 跳出外层循环
}
remain -= 1;
}
};
println!("The last counter is {counter}");
while

while和其他语言相同,条件为true执行循环

1
2
3
4
while counter < 10 {
counter += 1;
println!("counter = {counter}");
}
for

使用for x in seq的方式遍历数组

1
2
3
4
5
6
7
8
let food = ["breakfast", "lunch", "supper"];
for meal in food {
println!("Eat at {meal}");
}

for number in (1..3).rev() { // 左闭右开,rev()反转序列
println!("Eat time {number}");
}
匹配
match表达式

由多个分支组成,类似switch语句。每个分支包含一个模式和表达式,表达式以,结尾。

match的每个分支的表达式就是match的返回值,所以分支表达式的数据类型需要相兼容。

match必须用分支覆盖所有的情况,否则会编译错误,可以使用通配符匹配所有其他情况,这个通配符可以看作一个变量名,它匹配所有的其他相同类型的值,我们可以在这个分支的表达式中使用这个匹配变量,也可以使用_匹配任意值,但是我们不会引用它的值,可以看作是default。

模式的匹配是按编写顺序执行,所以不能把通配符分支放在前面,这样后面的分支无法被匹配。

1
2
3
4
5
match value {
patten1 => expression1,
patten2 => expression2,
patten3 => expression3,
}

在匹配的分支中可以使用模式的部分值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn handle_message(msg: Message) {
println!("match start");
match msg {
Message::Quit => println!("Quit"),
Message::Write(val) => {
println!("write {}", val);
}
Message::Move { x, y } => {
println!("move pos {},{}", x, y);
}
Message::ChangeColor(r, g, b) => {
println!("change color {},{},{}", r,g,b);
}
}
println!("match end");
}

let move_msg = Message::Move { x: 15, y: 20 };
handle_message(move_msg);

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i+1),
}
}

let roll = 100;
match roll {
5 => println!("luck num:{roll}"),
10 => println!("bad num:{roll}"),
left => println!("norm num:{left}"),// left是通配符
}

let config_max = Some(3u8);
match config_max {
Some(max) => println!("The max is {max}"),
_ => (), // 匹配所有其他值,但是不需要引用,这样没有编译警告,写法简单
}
if let表达式

如果只关系一种匹配的情况,而忽略其他match的分支时,可以使用if let简化match的写法。

1
2
3
4
5
6
7
let config_max = Some(3u8);
let config_none: Option<u8> = None;
if let Some(max) = config_max { // Some(max)等同于match中的模式
println!("The max is {max}"); // The max is 3
} else {
println!("None is input");
}
0%