Rust入门
- rust也是一门编译语言,而且很新,是二零一几年出的一个语言。但是rust的内存管理非常的安全,而且有堪比C++的运行效率
- 该rust学习笔记不适合纯新生或者是刚入门的新手,本人学习rust的主要目的是学习一下rust逆向和rust的pwn,更偏向于其内存管理等底层机制
rust环境
- 一开始入门不打算用IDEA开发效率高的环境,想一行一行敲,感觉这样基础更牢,所以就在Linux下搭建了rust环境。
- 在Linux下安装rust环境感觉是比在windows上容易的多,直接就是输入指令
1 | sudo apt update |
- 下载好后查看安装结果
1 | rustc --version |

- 先创建并编写一个简单的rust程序,先创建
main.rs文件,然后再编写如下内容
1 | fn main(){ |
- 使用
rustc编译,rustc main.rs,然后运行编译后的文件

Cargo的使用
cargo介绍
- Cargo是Rust编程语言的包管理系统和构建系统。它用于管理Rust项目的依赖项、编译代码、运行测试、构建项目和发布库。可以类似于Python的
pip包管理器 - 接下来介绍一下cargo的一些常用的命令
cargo new
- 使用
cargo启动新项目
1 | cargo new 项目名 --bin //创建一个可执行程序 |
- 使用
cargo new hello_world --bin,创建一个可执行程序它的目录如下- 其中
Cargo.toml,是由Cargo生成的配置文件,用于定义项目的元数据、依赖性、编译配置等。 src:里面存放的是源代码,src里面会生成一个main.rs文件,.rs后缀的文件就是rust的源代码文件
- 其中
1 | hello_world |
- 接下来查看一些
cargo.toml文件里面的具体信息,使用vim cargo.toml命令查看该文件里面的内容name:表示包的名字version:项目版本号edition:Rust的版本
1 | [package] |
注意:使用cargo new hello_wold命令生成的项目,目录也是与上方一样
cargo build
cargo build命令是编译使用cargo new所创建的项目
1 | cargon build |
- 直接在所创建的项目内使用
cargo build命令编译即可
1 | myheart@aaa ~/c/r/hello_world1 (master)> ls |
- 使用
cargo build命令后,项目的目录结构会发生改变,编译后基本上就是这样的目录,具体的了解即可。重点就是为可执行二进制文件
1 | hello_world |
rust hello_world
内容
- 首先先来一个hello_wrold
1 | fn main(){ |
- 使用
cargo build,将该源代码编译,也可以直接使用rustc编译编译,使用rustc ./main.rs - 编译后运行即可就会出现如下输出:
1 | myheart@aaa ~/c/r/h/src (master)> ./main |
- 接下来分析一下该程序:
- 首先
fn main()fn定义函数的关键字main(),就代表main函数,当运行该程序的时候,该程序就会从main函数这边开始运行- 如果声明函数的要参数的话,该参数必须在
()内部被声明 {}花括号就是函数体,rust要求所有函数体都要被花括号包裹起来
1 | fn main(){ |
- 然后
println!()函数:这个是输出函数,即打印函数,会将字符给打印到屏幕中。- 在这里需要强调:rust风格使用四个空格进行缩进,而不是使用Tab键
- 然后
println!是一个宏,如果单纯调用一个函数,那么就不会有!,即正常函数是这样的println() rust中所有以!结尾的调用都意味着是使用宏而不是普通的函数- 然后
rust代码行都会以分号来结尾。
1 | println!(“hello world!”); |
总结
- 关于rust编程风格
- rust要使用
fn关键字定义函数 - rust要使用
{}来表示函数体,rust要求所有函数都要被花括号包裹起来 - rust中缩进为
四个空格,而不是Tap键 - rust代码行都会以分号来结尾
; - rust中程序以main函数为程序的入口点
- rust要使用
- 补充一点:
rust语言也是使用//表示单行注释,
项目-猜数字
创建项目
-
接下来将编写一个猜数字的简单项目,利用该项目再来熟悉一下rust程序的编写
-
先使用
cargo创建一个guessing_game,进入该目录,cd guessing_game
1 | cargo new guessing_game |

- 然后我们先使用
cargo run命令将该项目编译运行一下,该命令可以用于快速迭代一个项目

编写项目
第一部分 基本结构
cargo run运行完之后就可以,可以使用编写程序了,编写main.rs- 该程序实现的是一个猜数字游戏,这是该游戏的第一部分,该部分会请求用户进行输入,并检查输入是否满足预期的格式。
1 | use std::io; |
- 写好后使用
cargo run命令再编译运行该程序

第一部分解释
导入包
use std::io,就是导入std标准库中的io模块,可以类比于Python的import io(虽然类比于C++会更合适)。- rust一开始会导入一些模块,该模块的作用域为每一段程序。这个导入的模块只包含了一小部分。
- 但是有些函数或者类没有在该模块中我们就要使用
use语句,导入其他的库和模块,就比如use std::io。 std::io这个模块在该部分的作用是获得用户输入的数据,如果我们没有use std::io这个语句,当我们使用io::stdin时就不能这么写,而是要写成std::io::stdin
声明变量
let mut guess:该语句是声明一个可变的变量,let是声明一个变量guess,mut(mutable)表示这个guess变量是可变的。并且程序会在栈内存中开辟一个空间,来存储guess变量的值。- 这里我们深刻来理解一下
mut,这个使得变量可变的关键字 - 当我们使用
let foo = 5时,该变量是不可变的,即之后的代码中,我们不能再将foo赋值给其他值。这里我们进行一个lab。当我们使用rustc lab1.rs编译时,会出现如下的报错
1 | fn main(){ |

- 当我们使用
let mun a=5时就可以对a进行修改
1 | fn main() { |
- 这时我们再编译就只会出现警告,而不是报错,可以正常编译,编译后也能正常运行

创建实例
let mut guess = String::new,该语句中String::new,会创建一个String的实例。String是标准库中的一个字符串类型,它在内部使用UTF-8格式编码,并可以按需求扩展自己的大小new是一个关联函数,并不是独立函数,它的作用创建某一类型的一实例。new会开辟一个堆内存空间,使用该函数开辟的堆内存空间是被自动管理的。并不需要我们手动管理String::new,就是将字符串类型与new函数关联起来,开辟一个字符串类型的堆内存空间,返回的是一个字符串对象。可以用处理字符串的方法来处理该对象。- 而
let mut guess = String::new,表示guess指向一个字符串对象,调用该变量可以打印出该字符串对象里面的字符串。
注意:可以简单理解guess指向该对象,但是并不是String::new返回值是单纯的堆地址,面向对象的内存管理抽象化了,是自动管理内存的。并不是像C语言那样将内存具体化,让开发者手动管理内存。(可以试着打印一些guess的值,它不会打印出地址,而是在该对象没有任何内容的时候会输出空。)
let mut guess = String::new,运行该语句的内存运行机制。面向对象的,可以看做是对结构体的一种扩展和运用。对于字符串这个对象,在rust语言中会是如下的结构体。而guess变量是指向一个字符串实例,所以guess变量存储着一个结构体。- 而我们在一个对象中有特定的方法可以操作该对象,可以理解为有专门的函数可以处理该结构体。
1 | struct String { |

输入处理
io::stdin会返回std::io::stdin的实例,该实例中保存了许多方法来处理终端的标准输入。比如.read_line这个方法就是用来读取用户的一行标准输入io::stdin().read_line(&mut guess),这行代码中.read_line(&mut guess)里面的&mut guess,是引用传参,&意味着当前参数是一个引用。- 引用操作的是与传递的参数同一块的内存,而不是像普通传参一样,复制一份对我们所复制的进行修改
注意:引用的参数与变量一样默认情况下是不可变的,所以我们需要使用mut关键字,表示该引用的参数是可变的
异常处理
io::stdin().read_line(&mut guess).expect("Failed to read line");这行代码其实是这么长- 当我们使用
.read_line方法的时候,使用结束后该方法会返回io::Result的值,Result是一个枚举类型,拥有Ok(字母O)和Err两个变体。执行成功Result的值为Ok,而在执行过程中出错,就会Result的值就为Err,并且附带失败的原因 - 而
expect方法就是用来处理Result的这个枚举类型,当expect接收到为Err,那么就会中断程序,并将传入的字符串参数显示出来。如果接收到为OK那么就会提取OK中附带的值,并返回给用户。
注意:如果没有expect这种异常处理,仍然可以通过编译,但是编译器有警告提示
println!占位符
- 最后这句
println!("a 的值是: {}", a)可以将存储的用户输入打印出来。 - 第一个参数是用来格式化字符串的,而花括号就相当于占位符
{},可以将后面的参数值插入用{}所占的位置,第一个花括号对应的第一个参数,第二个花括号对应的是第二个参数,以此类推。 - 接下来简单再来演示一下花括号的占位符,这个的字符串输出结果就会被变量
a、b、a+b的值给取代了。格式化字符串输出这个内部原理,可以上网了解一下C语言printf函数的内部实现。
1 | fn main(){ |

第二部分 随机数生成
- 为了增加游戏的可玩性,我们将使用随机数来生成一个数字,然用户去猜测。
- 在rust中,并没有把随机数生成函数,内置到标准库中,而是作为外部的
rand包提供给用户 - rust中的包(crate)代表了一系列源代码文件的集合
- 我们当前正在构建的项目是一个用于生成可执行程序的二进制包
rand包是一个用于重复功能的库包
Cargo之前有说过类似于python中的pip,是用来帮助我们管理和使用第三方库和包,所以在使用rand这个包的时候就先要使用Cargo导入rand包。- 我们先在该目录下对
Cargo.toml这个文件进行编辑

- 在
dependencies下方导入rand包,即添加一行rand="0.3.14"。 rand="0.3.14"表示任何与0.3.14版本公共API相兼容的版本

- 修改完保存后,不要修改任何rust代码,直接使用
cargo build重构这个项目,Cargo就会下载rand包到本地中

- 接下来我们使用随机数,增加猜数字的趣味性和可玩性
1 | use std::io; |
第二部分解释
-
use rand::Rng:这里的Rng是一个train(特征),它定义了随机数生成的方法集合,这使得我们可以访问随机数生成的方法。 -
let secret_number = rand::thread_rng().gen_range(1,101),这段代码中rand::thread_rng()会返回一个特定的随机数生成器,位于本地线程空间,并通过操作系统获得随机数种子,之后就利用gen_range这个方法生成随机数gen_range这个方法是接收俩个数字做为参数,并生成包含下限但是不包含上限的随机数,在本代码中参数为(1,101)就会生成1~100之间的随机数
-
运行该代码后就会出现如下结果:

第三部分 比较和隐藏数字
- 既然是猜数字,就不能让被猜的数字显示出来
- 注意该代码编译时会报错,无法编译出二进制程序
1 | use std::io; |
第三部分解释
-
use std::cmp::Ordering,该段代码从标准库引入了std::cmp::Ordering的类型。Ordering也是枚举类型,有Less、Greater、Equal3个变体,用于表示比较数字后的结果 -
match guess.cmp(&secret_number):guess.cmp中cmp方法,该方法接收secret_number的引用,然后返回secret_number与guess比较后的结果。返回的结果就是Ordering枚举类型match表达式就用来匹配返回的结果,从而决定下一步应该执行什么代码- 假设我们要猜的数是
38,而我们猜50,比较返回的结果就是Ordering::Greater变体。 - match就会去按顺序匹配对应变体,先匹配
Ordering::Less,不是该变体就不执行println!("Too small!") - 然后就会接下去匹配
Ordering::Greater变体,匹配成功就执行println!("Too big!"),执行完后就退出匹配了
- 假设我们要猜的数是
-
如果要隐藏代码就直接注释掉
println!("The secret number is: {}", secret_number);,在该段代码的前面添加// -
尝试编译第三部分代码,发现我们有一处错误,就是
secret_number是整型,而guess是字符串对象,他们无法比较

第四部分 类型转换
- rust有一个静态强类型系统,同时还有自动进行类型推导的能力。
- 当我们使用
let guess = String::new()时,rust会自动将变量推导为String - 而
secret_number可能是i32(32位整数)、u32(32位无符号整数)、i64(64位整数),rust会默认将secret_number视作i32 - 但是
i32和String无法直接对比,所以编译器会报错 - 所以为了正常比较,应该将
guess转成u32
1 | use std::io; |
第四部分解释
-
let guess: u32 = guess.trim().parse().expect("Please type a number!");- 这边我们又定义了一个
guess变量,rust允许定义相同变量名的变量,该变量会覆盖之前的变量,达到隐藏原变量的效果(虽然这种操作有点像弱类型语言的操作,但是rust是强类型语言)。 .trim()方法用来去掉首尾空字符,返回处理后的字符串实例(该方法用来去除字符串末尾的\n),使用read_line会读取输入结束的换行符parse方法将当前字符串解析成数值,let guess: u32显示声明要转换为32位无符号整数,变量后面加个:和再跟着要转换的数据类型- 由于rust有自动进行类型推导的能力,当我们转换
guess为u32时,guess会和secret_number比较,所以secret_number也会被自动推导为u32 parse方法非常容易出错,这时就要使用expect来进行异常处理
- 这边我们又定义了一个
-
我们再次
cargo run一下程序,发现成功运行了

第五部分 循环
- rust中,loop关键字会创建一个无限循环,加入程序代码中即可
1 | use std::io; |
- 编译运行后就可以进行了,但是会出现不想玩时无法正常退出游戏,或者是在猜测成功后无法正常退出

- 这时我们就要添加一个
break的语句,从而在猜对后退出循环
1 | use std::cmp::Ordering; |
- 继续编译运行一下,这样猜对后就可以正常退出了

第六部分 异常处理输入
- 当我们在输入的时候输入了非数字,而是输入了字母就会导致程序异常退出,非常不好
- 这时我们就要对
parse()方法的返回值做处理,这里使用match进行处理,而不使用expect进行异常处理 - 这种处理是根据
parse()返回值这个Result的枚举进行匹配,这里Err(_)中_是一个通配符,表示匹配所有的Err()值 continue语句会使得程序跳转到下一次循环- 最后再删除
println!("The secret number is: {}", secret_number);即可得到完整代码
1 | use std::io; |
- 使用
cargo run编译运行一下就可以开始玩游戏了

Cargo的使用(全)
- 之前的
Cargo命令是简单使用,为了就是快速入门Cargo然后,较快的上手rust,现在就归纳一下cargo的常用命令,Cargo虽然学习编程可能不怎么能体现他的重要性,但是在编写项目的便可以体现出cargo的优势
cargo new
- 使用
cargo启动新项目
1 | cargo new 项目名 --bin //创建一个可执行程序 |
- 使用
cargo new hello_world --bin,创建一个可执行程序它的目录如下- 其中
Cargo.toml,是由Cargo生成的配置文件,用于定义项目的元数据、依赖性、编译配置等。 src:里面存放的是源代码,src里面会生成一个main.rs文件,.rs后缀的文件就是rust的源代码文件
- 其中
1 | hello_world |
- 接下来查看一些
cargo.toml文件里面的具体信息,使用vim cargo.toml命令查看该文件里面的内容name:表示包的名字version:项目版本号edition:Rust的版本
1 | [package] |
注意:使用cargo new hello_wold命令生成的项目,目录也是与上方一样
cargo build
cargo build命令是编译使用cargo new所创建的项目
1 | cargon build |
- 直接在所创建的项目内使用
cargo build命令编译即可
1 | myheart@aaa ~/c/r/hello_world1 (master)> ls |
- 使用
cargo build命令后,项目的目录结构会发生改变,编译后基本上就是这样的目录,具体的了解即可。重点就是为可执行二进制文件
1 | hello_world |
cargo run
- 该
cargo命令就是编译并运行该rust程序,这里我们先使用cargo new hello_world2

- 然后使用然后我们使用
cd命令,进入hello_world2这个目录 - 再输入
cargo run这个命令,即可编译并运行该cargo这个文件
1 | myheart@aaa ~/c/r/hello_world2 (master)> cargo run |
导入包
- 当我们要引入外部包或者库的时候我们就要使用
cargo这个包管理器 - 我们先使用
cargo new创建了一个新的项目,然后再Cargo.toml这边导入包 - 导入包后马上执行
cargo build,这样就可以导入包了。cargo会先在本地寻找相关包,如果没有相关的包就会远程下载该包,然后放入本地中。 - 之后的项目如果再使用该包就会在本地中导入,无需再次下载

