1 安装
在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
#curl https://sh.rustup.rs -sSf | sh
# rustup update # 更新rust
安装完毕后刷新环境变量:
source ~/.cargo/env
上面安装好rust的相关软件,然后在.bashrc里面添加环境变量,重启终端生效,或者执行source .bashrc
,添加内容如下:
export PATH=$PATH:$HOME/.cargo/bin:$PATH #$
测试:
cargo -V # 查看cargo版本
rustc -V # 查看rust版本
安装 rust 的 vim 插件:
git clone https://github.com/rust-lang/rust.vim.git
mv rust.vim .vim/bundle/
VScode上编写、调试rust
1 安装号VScode后,再从商店里面安装两个插件:
- Rust(rls)
- Native Debug
重启生效;
2 用vscode打开创建好的rust项目,点击左侧调试按钮,在这里点击创建launch.json文件
,选择环境GDB
;
3 当前目录下会生成.vscode文件夹,在该目录下手动创建两个文件:tasks.json、launch.json,添加如下内容:
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command":"cargo",
"args": ["build"]
}
],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new",
"showReuseMessage": true,
"clear": false
}
}
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "gdb",
"preLaunchTask": "build",
"request": "launch",
"target": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
"cwd": "${workspaceFolder}"
}
]
}
添加完毕后,即可点击按钮调试;
2 编写、运行rust程序
- 创建 hello.rs 文件
- 编写内容:
fn main() {
println!("hello, world!");
}
- 编译
rustc hello.rs
- 运行
./hello
- 查看文档
cargo doc --open
3 cargo 命令
- 新建工程
cargo new hello_cargo
- 编译
cargo build
cargo build --release
- 运行
cargo run
- 检查
``bash
cargo check
用于检查是否能编译,一般检查语法时用这个命令,速度比 cargo build 快;
4 常用语句
- 获取键盘输入
fn main() {
let mut a = String::new();
std::io::stdin().read_line(&mut a).expect("Failed to read line.");
println!("{}",a);
}
5 变量与常量
5.1 变量
- 不可变变量(默认)
let x = 1;
不能第二次赋值
- 可变变量
let mut x = 1;
5.2 常量
const MAX_POINTS: u32 = 100_000;
5.3 隐藏
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("{}", x);
}
重复创建x变量会隐藏前面的变量,在每次let时,x的类型可以改变,本质是重新创建了一个变量;
6 数据类型
有4种标量类型:整形、浮点型、布尔型、字符型
- 整形
不同长度类型:
长度 | 有符号 | 无符号 |
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
atch | isize | usize |
不同进制的表示方式:
进制 | 例子 |
十 | 98_222 或者 98222 |
十六 | 0xff |
八 | 0o77 |
二 | 0b1111_0000 或 0b11110000 |
byte(u8) | b’A’ |
- 浮点型
let x = 2.0; // f64,默认64位
let y: f32 = 3.0; // f32
- 布尔型
let t = true;
let f: bool = false;
- 字符型
let c = 'a';
6.1 数学运算符
Rust 中不同数据类型的常量定义参考这里 Rust 中自带有数学运算,但数学运算需要特定的数据类型,所以在运算前要确保数据类型已经转换为对应的格式:
println!("5+4= {}", 5 + 4);
println!("5-4= {}", 5 - 4);
println!("5*4= {}", 5 * 4);
println!("15/4= {}", 15 / 4);
println!("18%4= {}", 18 % 4);
println!("2^6 = {}", 2i32.pow(6));
println!("sqrt 9 = {}", 9f64.sqrt());
println!("27 cbrt 9 = {}", 27f64.cbrt());
println!("Round 1.45 = {}", 1.45f64.round());
println!("Floor 1.45 = {}", 1.45f64.floor()); // 返回小于它的最大整数
println!("Ceiling 1.45 = {}", 1.45f64.ceil());
println!("e ^2 = {}", 2f64.exp());
println!("log(2)= {}", 2f64.ln());
println!("log10(2) = {}", 2f64.log10());
println!("90 to Radians = {}", 90f64.to_radians());
println!("PI to Degrees = {}", 3.1415f64.to_degrees());
println!("sin(3.1415) = {}", 3.1415f64.sin());
println!("Max(4,5) = {}", 4f64.max(5f64));
println!("Min(4,5) = {}", 4f64.min(5f64));
7 元组和数组
- 元组
元组的元素可以是不同的类型,元组长度固定,一旦声明,其长度不会改变;从元组中取出元素有下面两种方式:
fn main() {
let tup = (1, 1.1, 200);
// let tup: (i32, f64, u64) = (1, 1.1, 200);
let (x, y, z) = tup;
let a = tup.1;
println!("{}",y);
println!("{}",a);
}
- 数组 array
其元素的类型必须一样:
let a = [1, 2, 3, 4];
let b = ["abc", "abcd", "abcde"];
let c: [i32; 5] = [1, 2, 3, 4, 5];
let d = [3; 5];
println!("{}", d[0]);
访问数组时,如果索引超出了数组的范围,编译可以正常通过,但运行时会报这个访问超出范围的错;
- slice
slice 是对数组 array 的切片,所以通过 slice 可以获取 array 的部分或全部的访问权限:
let arr = [1, 2, 3, 4, 5, 6];
let slice_complete = &arr[..]; // 获取全部元素
let slice_middle = &arr[1..4]; // 获取中间元素,最后取得的Slice为 [2, 3, 4] 。切片遵循左闭右开原则。
let slice_right = &arr[1..]; // 最后获得的元素为[2, 3, 4, 5, 6],长度为5。
let slice_left = &arr[..3]; // 最后获得的元素为[1, 2, 3],长度为3。
- 函数类型
可以创建变量指向函数:
fn foo(x: i32) -> i32 { x+1 }
let x: fn(i32) -> i32 = foo;
assert_eq!(11, x(10));
8 函数
注意函数最后一句没有 ‘;’ 号:
fn main() {
let x = plus_one(5);
println!("{}", x);
}
// 这里函数的传入参数和传出参数都是所有权,如何使用引用权、借用权在后文描述
fn plus_one(x: i32) -> i32 {
x + 1
}
9 控制流
9.1 if
注意 if 的条件不加括号,和c语言不同,其他地方和c语言一样:
fn main() {
let x = true;
let y = 1;
if x && (y == 1) {
println!("1")
} else if y == 2 {
println!("2")
} else {
println!("3")
}
}
if 表达式也可用在 let 语句的右侧,但需要在 if 表达式的最后面加上 ‘;’,并且每个条件返回的数据类型要一致,如:
fn main() {
let condition = true;
let n = if condition {
5
} else {
6
};
println!("{}", n);
}
9.2 loop
如果不终止,该循环会一直执行下去:
fn main() {
loop {
println!("again!");
}
}
可以用 break 语句来终止循环,break 后面可以跟上要返回的值:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("{}", result);
}
9.3 while
fn main() {
let mut n = 3;
while n != 0 {
println!("{}", n);
n = n - 1; // rust不能使用 n++ 或 n--
}
}
9.4 for
类似于 python:
fn main() {
let a = [10, 20, 30, 40];
for i in a.iter() {
println!("{}", i); // 依次打印 10, 20, 30 40
}
}
fn main() {
for i in (1..5).rev() {
println!("{}", i); // 依次打印 4, 3, 2, 1
}
}
9.5 break 和 continue
这两个关键字在 rust 中也可以使用,同其他语言一样;
9.6 label
由于经常会使用到嵌套循环,此时如果让 break 和 continue 去直接操作对应层的循环,就可以使用到 label 功能:
'outer: for x in 0..10 {
'inner: for y in 0..10 {
if x % 2 == 0 { continue 'outer; }
if y % 2 == 0 { continue 'inner; }
println!("x: {}, y: {}", x, y);
}
}
- 注释
// abcd
/* abcd */
- 新建变量
let x = 20;
- 打印
println!("x = {}", x); // {}与x对应
10 所有权
先解释以下堆和栈,一般而言,数据占用已知且固定大小,就可以把这类数据存放在栈中,如果数据占用的大小未知,而且是在程序中产生,那么这类数据会放在堆中;栈在内存中是连续的内存空间,用进栈、出栈的方式管理,堆的产生需要向系统申请一段足够大的内存空间,位置不连续;访问堆的时间比栈长;
RUST 的所有权主要用于管理内训,所有权有以下几个规则:
- Rust中的每一个值都有一个被称为所有者的变量;
- 值有且只有一个所有者;
- 当所有者(变量)离开作用域,这个值将被丢弃;
10.1 String 类型
这个类型不仅仅存放在栈上,它的值会存放在堆上,所以能够存储在编译时未知大小的文本;相关的一些使用如下:
let mut s = String::from("hello");
s.push_str(", world!");
println!("{}", s);
10.2 变量与数据交互的方式
10.2.1 方式一:移动
分析几个例子:
- 移动普通数字变量
let x = 5;
let y = x;
在上面的代码中,两个变量的值都为5,因为它们内存大小固定,将存放在栈中,栈里的变量移动时相当于复制;
- 移动String类型
let s1 = String::from("hello");
let s2 = s1;
String类型的值存放在堆中,所以应该会有深拷贝和浅拷贝的问题,不过在Rust中,这里s2 = s1
更像是浅拷贝,但与浅拷贝不同的是这里s1将会变得无效,同时,s1把所有权交给了s2,比如下面这段代码不能运行:
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1);
换句话说,s1把所有权交给了s2,自己就失效了,因为一个值只有一个变量具有所有权;下面这段代码能运行:
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s2);
10.2.1 方式二:克隆
克隆也是深拷贝,不仅仅是栈上的数据,堆上的数据也被复制了,如下:
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
10.3 所有权与函数
先看两段代码:
fn main() {
let s: i32 = 1;
fun1(s);// s复制了一份传入函数
// 到这里s还能使用
}
fn fun1(arg: i32) {
printl!("{}", arg);
}
fn main() {
let s = String::from("hello");
fun2(s);// s传递到函数中,自己销毁了(其实是s对应的栈数据销毁了,复制了一个新的栈数据替代它,并传入函数)
// 到这里s不能使用了
}
fn fun2(arg: String) {
printl!("{}", arg);// arg会销毁
}
对比上面两组代码可以发现,传递变量到函数中,默认使用的是“=(移动)”操作,如果这个变量值存在于栈中(如i32),就相当于复制,如果还存在于堆中(如String),原数据将会被销毁;
对于这种所有权的传递方式,怎么能让传入参数不丢失呢?解决方案是在函数的末尾又传出来,如下:
fn main() {
let s1 = String::from("hello");
let s2 = fun1(s1);
}
fn fun1(s: String) -> String {
printl!("{}", s);
s
}
也可以返回多个参数:
fn main() {
let s1 = String::from("hello");
let (s2, len) = fun1(s1);
}
fn fun1(s: String) -> (String, usize) {
printl!("{}", s);
let length = s.len();
(s, length)
}
10.4 引用和可变引用
上面的函数采用所用权传递,对传入参数影响较大,对此,可以通过引用
来解决这个问题,如下:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
引用就是在变量类型的前面加上 &
符号,默认情况下函数就只有使用它的权利,没有更改、销毁的权利,如果要有修改的权利,就是可变引用
,如下:
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
10.5 Slice 类型
11 结构体
11.1 定义、使用结构体
- 1 定义结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
- 2 实例化结构体
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
- 3 改变成员的值
如果要改变成员的值,那么在实例化结构体的时候就要定义成mut
类型的,然后就可以在外部更改:
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("xxxxx.com");
- 4 结构体作为函数参数
// 调用函数
let user1 = build_user(String::from("xxx.com"), String::from("xxx"));
// 定义函数
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
// 为了简化上面的重复名称,可以替换成下面这样:
// email,
// username,
active: true,
sign_in_count: 1,
}
}
- 5 如果新建的实例内容和已经存在的实例有重复的,可以用下面的方法简化代码:
// 不简化
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
// 简化
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1 // ..表示余下的所有其他成员都保持一致
};
- 6 也可定义与元祖类似的结构体:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
11.2 打印结构体
显然直接用 println!("{}", user1);
是不行的,打印之前需要在开头加上 #[derive(Debug)]
,并且使用 println!("{:?}", user1);
来打印,打印信息如下:
User { name: "123", email: "345", age: 1, active: true }
也可以改变打印的格式,比如是否换行,这只需要改变打印的参数,将 {:?}
改为 {:#?}
;
11.3 给结构体添加方法
观察 impl 中各个函数的参数:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle { // impl + 结构体名
fn area(&self) -> u32 { // 也可以换成 (&mut self)
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool { // 可以定义多个方法,self是自身
self.width > other.width && self.height > other.height
}
}
// 可以多处使用 impl 块
impl Rectangle {
// 参数中没有self,称关联函数,外部调用是用 :: 符号
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("The area of the rectangle is {} square pixels.",rect1.area());
// 调用关联函数
println!("{:?}", rect1::square(3));
}
12 枚举
12.1 定义枚举
- 1 定义
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
- 2 作为函数参数
``rust
enum IpAddrKind {
V4,
V6,
}
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);
route(IpAddrKind::V6); - 3 每个成员可以有自己的数据类型
如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
数据类型也可以是结构体:
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
- 4 也可以用 impl 定义方法
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
- 5 用 option 解决空值问题
enum Option<T> {
Some(T),
None,
}
可以这样赋值:
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
如果变量的值有可能为空,就需要用 Option 来修饰,然后通过判断,只对其不为空的值做处理,如:
let mut a: Option<i32>;
//a = None;
a = Some(1);
// 解析 Option 方法一:
if let Some(i) = a { // 这里的 i 就相当于 a 里的值,因为 a 是个Option,值不能直接访问
println!("{}", i);
}
// 解析 Option 方法二:
let v = a.unwrap();
println!("{}", v);
// 解析 Option 方法三:
match a.as_mut() {
Some(v) => println!("{}", *v),
None => {},
}
// 判断 Option 是否有值
if a.is_some() {
println!("有值" );
}
- 6 Result<T, E> 用于函数的返回,处理错误情况
// 定义
enum Result<T, E> {
Ok(T),
Err(E),
}
// 使用
let f = File::open("hello.txt"); // 返回值就是 Result<T, E> 类型
match f {
Ok(file) => {
println!("File opened successfully.");
},
Err(err) => {
println!("Failed to open the file.");
}
}
12.2 match
- 1 match 用于过滤枚举的值,逻辑像 swich,哪一个条件匹配了就执行该条件后面的语句:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
// 这里可以用 impl 方法把函数绑在 Coin 中
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
let coin = Coin::Nickel;
println!("{}", value_in_cents(coin));
}
- 2 枚举还可以嵌套,match 同样可以使用:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
- 3 关于
Option<T>
,和枚举一样使用:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
- 4 match 需要把所有的情况罗列出来,如果不想把所有的罗列出来,可以使用通配符
_
,如:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (), // 其他情况会自动罗列完
// 如果不罗列完所有情况,编译会查得出来
}
如果很多种情况,而只需要操作其中一种情况,就可以用上面的方式解决,代码像这样:
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
对此,有一个更简单的方法,就是使用 if let
:
# let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
12.3 if let简单控制流
如果只想匹配枚举中的单个选项,可以使用 if let 来简化 match,如:
let some_u8_value = Some(0u8);
//match some_u8_value {
//Some(3) => println!("three"),
//_ => (),
//}
if let Some(3) = some_u8_value {
println!("three");
}
13 组织管理
一般的工程项目都会涉及到很多个文件,并且需要在不同的文件中是实现不同的功能,如何将这些文件组织在一起是首先要解决的问题;Rust对此的处理能让项目文件的结构很清晰,各种模块、功能的分类结构和文件夹结构非常相似;
13.1 创建模块、调用模块
在新建的Ruat项目中,只存在 src/main.rs 源程序,我们另外创建一个 my_lib.rs 文件,src 文件结构如下:
── src
├── my_lib.rs
└── main.rs
在 my_lib.rs 文件中添加 fun1 函数和 module1 模块,代码如下:
my_lib.rs # 文件名本身就是模块名,如 my_lib
,文件里的模块等归属在它下面
pub fn fun1() {} // pub 表示公开,外部能访问,不使用的话默认私有
pub mod module1 {
pub mod module1_1 { // 模块可以内嵌
pub fn fun2() {}
}
pub fn fun3() {}
}
在 main.rs 文件中调用 fun1函数 和 module1 中的函数:
main.rs
mod my_lib; // 引用 my_lib.ts 文件这个模块
pub use crate::my_lib::module1;
fn main() {
my_lib::fun1(); // 调用 my_lib.rs 中的 fun1 函数
module1::module1_1::fun2(); // 调用 my_lib.rs 中的模块
module1::fun3();
}
13.2 模块的结构和文件夹的结构关系
再据一个例子来说明其组织管理和文件夹结构的关系,在上面的代码基础上,在 my_lib 中再添加模块 module2:
my_lib.rs
pub fn fun1() {} // pub 表示公开,外部能访问,不使用的话默认私有
pub mod module1 {
pub mod module1_1 { // 模块可以内嵌
pub fn fun2() {}
}
pub fn fun3() {}
pub mod module2;// 需要创建一个 rs 文件来添加里面的内容
然后在 my_lib.rs 的统计目录下创建 my_lib 文件夹,并在 my_lib 文件夹下创建 module2.rs 文件,并添加代码:
my_lib.rs
pub fn fun4() {}
调用方式和前面一样:
main.rs
mod my_lib;
pub use crate::my_lib::module2;
fn main() {
module2::fun4();
该实验的文件结构:
── src
├── my_lib
│ └── module2.rs
├── my_lib.rs
└── main.rs
13.3 crate、super、use、as关键词
crate 表示根目录
super 表示父目录,也就是父模块
use 使用模块的作用域
as 给作用于提供新名称
use std::io::Result as IoResult;
13.4 创建模块
用下面命令可以单独创建库工程(或像前文一样直接添加 .rs 文件,并在里面实现模块):
cargo new --lib <库名称>
创建完毕后,里面自带测试代码:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
执行下面命令即可测试:
cargo test
//cargo test -- --nocapture
注:库的文档通常写在文档注释中,注释///
在任何项之前开始,或//!
记录父项;
13.5 使用外部包
13.6 推荐的目录结构
.
├── Cargo.lock
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── main.rs
│ └── bin/
│ ├── named-executable.rs
│ ├── another-executable.rs
│ └── multi-file-executable/
│ ├── main.rs
│ └── some_module.rs
├── benches/
│ ├── large-input.rs
│ └── multi-file-bench/
│ ├── main.rs
│ └── bench_module.rs
├── examples/
│ ├── simple.rs
│ └── multi-file-example/
│ ├── main.rs
│ └── ex_module.rs
└── tests/
├── some-integration-tests.rs
└── multi-file-test/
├── main.rs
└── test_module.rs
Cargo.toml 与 Cargo.lock 存储在项目的根目录中。
源代码进入src
目录。
默认库文件是src/lib.rs
。
默认的可执行文件是src/main.rs
。
其他可执行文件可以放入src/bin/*.rs
。
集成测试进入tests
目录(单元测试进入他们正在测试的每个文件中)。
示例可执行文件放在examples
目录中。
基准测试进入benches
目录。
14 数据集合
Rust 中广泛使用的数据集有3类:Vector、String、HashMap;这些数据集的内容一般存放在堆上,由程序运行时动态分配;
14.1 Vector
- 新建 vector:
let v: Vec<i32> = Vec::new(); // 创建空的vector,类型i32
let v: Vec<i32> = Vec::with_capacity(10); // 和上面new一样,但会预先分配10个成员空间,效率稍微比new高点
let v = vec![1, 2, 3]; // 新建一个拥有值1、2、3的 Vec<i32>
- 更新 vector :
let mut v = Vec::new();
v.push(5); // 增加元素
v.push(6);
v.push(7);
v.push(8);
- 读取 vector 的元素
读取有两种方式:索引法、get方法,如果超出了范围,索引法会在程序运行时报错,而 get 方法会返回 None,在实际运用中考虑这两者的区别;
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
- 遍历 vector
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
也可以在遍历的时候改变它的值:
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
- 在 vector 中存放不同类型的值可以搭配枚举来实现:
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
14.2 String
- 新建字符串string
// 创建空的string
let mut s = String::new();
// 从 str -> string
let data = "initial contents";
let s = data.to_string();
// 该方法也可直接用于字符串字面值:
let s = "initial contents".to_string();
// 从 string -> string
let s = String::from("initial contents");
- 更新string
let mut s = String::from("foo");
s.push_str("bar"); // 追加字符串
s.push('l'); // 追加字符
- 拼接string
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3); // 多个字符串连接
- 索引string
let s1 = String::from("hello");
//let s1 ="sdfaasdf";
let h = s1[0]; // 错误!不能这样索引
println!("{}, {}", &hello[0..1], hello);
- 遍历string
let s = String::from("hello world!");
for c in s.chars() {
println!("{}", c); // 遍历每一个char,结果是字符
}
for c in s.bytes() {
println!("{}", c); // 遍历每一个byte,结果是u8类型的数字
}
- string 和 int 互换
let int_value = 5;
let string_value = int_value.to_string();//int to String
let back_int = string_value.parse::<i32>().unwrap();//类型可以换乘下面的 u32 、i16 等
//let back_int = string_value.parse::<u32>().unwrap();
//let back_int = string_value.parse::<i16>().unwrap();
- string 的常用方法:
参考这里
方法 | 原型 | 说明 |
new() | pub const fn new() -> String | 创建一个新的字符串对象 |
to_string() | fn to_string(&self) -> String | 将字符串字面量转换为字符串对象 |
replace() | pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String | 搜索指定模式并替换 |
as_str() | pub fn as_str(&self) -> &str | 将字符串对象转换为字符串字面量 |
push() | pub fn push(&mut self, ch: char) | 再字符串末尾追加字符 |
push_str() | pub fn push_str(&mut self, string: &str) | 再字符串末尾追加字符串 |
len() | pub fn len(&self) -> usize | 返回字符串的字节长度 |
trim() | pub fn trim(&self) -> &str | 去除字符串首尾的空白符 |
split_whitespace() | pub fn split_whitespace(&self) -> SplitWhitespace | 根据空白符分割字符串并返回分割后的迭代器 |
split() | pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P> | 根据指定模式分割字符串并返回分割后的迭代器。模式 P 可以是字符串字面量或字符或一个返回分割符的闭包 |
chars() | pub fn chars(&self) -> Chars | 返回字符串所有字符组成的迭代器 |
14.3 HashMap
HashMap 由 键-值组成,键类型是string,值类型是i32;
- 新建HashMap
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10); // Blue 是键,10 是值
scores.insert(String::from("Yellow"), 50);
- 利用 vector 生成 HashMap:(其实是 vector -> 元祖 -> HashMap,这两个阶段用到了 zip 和 collect() 方法)
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); // HashMap<_, _> 类型注解不能少
- 也可以用插入 vector 的方法生成 HashMap:
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 这里 field_name 和 field_value 不再有效,所有权交给了map
- 访问 HashMap 中的值
可以通过 get 方法并提供对应的键来从哈希 map 中获取值,如果在里面没有对用的值,get 会返回 None:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name); // 根据健找值
for (key, value) in &scores {
println!("{}: {}", key, value); // 访问 HashMap 中的每一对键值
}
- 更新 HashMap
1 覆盖一个值
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores); //这会打印出{"Blue": 25},原始的值10则被覆盖了。
- 2 只在键没有对应值时插入
如果 HashMap 中不存在这个键值就插入,否则忽略:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores); // 打印出 {"Yellow": 50, "Blue": 10}
- 3 根据旧值更新一个值
下面代码是计数文本中每一个单词分别出现了多少次:
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map); // 打印结果 {"world": 2, "hello": 1, "wonderful": 1}
15 迭代器 Iterarot
Rust 语言中的集合包括 数组( array )、向量( Vect! )、哈希表( map )等,我们可以简单的使用 iter()
和 next()
方法来完成迭代:
fn main() {
//创建一个数组
let a = [10,20,30];
let mut iter = a.iter(); // 从一个数组中返回迭代器
println!("{:?}",iter);
//使用 next() 方法返回迭代器中的下一个元素
println!("{:?}",iter.next());
println!("{:?}",iter.next());
println!("{:?}",iter.next());
println!("{:?}",iter.next());
for i in iter {
// 遍历 iterarot
}
for (index, data) in iter.enumerate() {
// 遍历 iterarot 并携带 索引
}
}
输出结果:
Iter([10, 20, 30])
Some(10)
Some(20)
Some(30)
None
- 迭代器类型常用的有3种:
方法 | 描述 |
iter() | 返回一个只读可重入迭代器,迭代器元素的类型为 &T(借用) |
into_iter() | 返回一个只读不可重入迭代器,迭代器元素的类型为 T(转移所有权) |
iter_mut() | 返回一个可修改可重入迭代器,迭代器元素的类型为 &mut T(可变借用) |
使用如下:
let names = vec!["简单教程", "简明教程", "简单编程"];
for name in names.iter() {
match name {
&"简明教程" => println!("我们当中有一个异类!"),
_ => println!("Hello {}", name),
}
}
println!("{:?}",names); // 迭代之后可以重用集合
}
16 闭包
闭包就是在一个函数内创建立即调用的另一个函数,没有函数名称
,闭包不用声明返回值
,但它却可以有返回值,闭包有时候有些地方又称之为 内联函数。这种特性使得闭包可以访问外层函数里的变量
;
闭包格式:
||{
// 闭包的具体逻辑
}
创建闭包,并赋给一个变量:
fn main(){
let is_even = |x| {
x%2==0
};
let no = 13;
println!("is even ? {}", is_even(no)); // 判断是否为偶数
}
闭包也可以访问外部的变量:
fn main(){
let val = 10;
let closure2 = |x| {
x + val // 访问作用域外部的变量
};
println!("{}",closure2(2));
}
17 指针
17.1 原始引用
Rust 支持两种原始引用:
- 不可变原始指针
*const T
- 可变原始指针
*&mut T
用 as
操作符可以将引用转为原始指针:
let mut x = 10;
let ptr_x = &mut x as *mut i32;
let y = Box::new(20);
let ptr_y = &*y as *const i32;
// 原生指针操作要放在unsafe中执行
unsafe {
*ptr_x += *ptr_y;
}
assert_eq!(x, 30);
17.2 函数指针
函数指针可以作为函数的参数
和返回值
;
作为参数:
pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{
op(a, b)/// 通过函数指针调用函数
}
fn sum(a: i32, b: i32) -> i32 {
a + b
}
let a = 2;
let b = 3;
assert_eq!(math(sum, a, b), 5);
作为返回值:
fn is_true() -> bool { true }
fn true_maker() -> fn() -> bool { is_true } // 函数的返回值是另外一个函数
assert_eq!(true_maker()(), true); // 通过函数指针调用函数
17.3 智能指针
-
Box<T>
是指向类型为T的堆内存分配值的智能指针; - 当
Box<T>
超出作用域范围时,将调用其析构函数,销毁内部对象,并自动释放内存。 - 可以通过解引用操作符来获取
Box<T>
中的T
#[derive(Debug, PartialEq)]
struct Point {
x: f64,
y: f64,
}
let box_point = Box::new(Point { x: 0.0, y: 0.0 });
let unboxed_point: Point = *box_point;
assert_eq!(unboxed_point, Point { x: 0.0, y: 0.0 });
y = Box::new(x) // 创建一个指向x的指针
std::mem::drop(x) // 手动提前释放x
Box<T>
Rc<T>
允许相同数据有多个所有者;Box<T>
和RefCell<T>
有单一所有者。RefCell<T>
18 泛型
18.1 枚举泛型
enum Option<T> {
Some(T),
None,
}
18.2 函数泛型
fn takes_anything<T>(x: T) {
// ...
}
18.3 结构体泛型
struct Point<T>{
x:T,
y:T,
}
// 给结构体定义方法
impl <T> Point<T>{
fn get_x(&self)->&T{ // 泛型参数都可以使用该方法
return &self.x;
}
}
impl Point<f32>{
fn distiance_from_origin(&self)->f32{ // 只有f32类型参数可以使用该方法
return (self.x.powi(2) + self.y.powi(2)).sqrt();
}
}
fn main() {
let int:Point<i32> = Point{x:4, y:4};
let fl:Point<f32> = Point{x:2.0, y:2.0};
println!("{}, {}, {}, {}", int.x, fl.y, int.get_x(), fl.distiance_from_origin());
}
18.4 trail
trail 用来实现多态。类似C++中的接口;
- 基本使用
pub trait Summary{
fn summarize(&self)->String; // 在这里可以实现该方法,作为默认方法
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summarizable for NewsArticle { // 有这句话才会将trail应用到结构体中
fn summary(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
- trail 作为参数
// 续"基本使用"的代码
pub fn notify(item:impl Summary){ //在 notify 函数体中,可以调用任何来自 Summary trait 的方法
println!("参数:{}", item.summarize())
}
fn main() {
let article = NewsArticle {
headline: String::from("NewsArticle_headline"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
};
notify(article); //值的所有权
}
此外,Rust还提供了另一种语法糖来,即Trait限定
,我们可以使用泛型约束的语法来限定Trait参数;
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
这样的语法糖可以在多个参数的函数中帮助我们简化代码,下面两行代码就有比较明显的对比:
pub fn notify(item1: impl Summary, item2: impl Summary) {
pub fn notify<T: Summary>(item1: T, item2: T) {
如果某个参数有多个trait限定,就可以使用+来表示
pub fn notify<T: Summary + Display>(item: T) {
/* 或者写成这样更漂亮
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{*/
- trail 作为返回值
// 续"基本使用"的代码
fn returns_summarizable() -> impl Summary {
NewsArticle {
headline: String::from("NewsArticle_headline"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
}
}
fn main() {
let al = returns_summarizable();
println!("{}, {}", al.summarize(), al.ari_authon());
}
19 多线程
19.1 创建多线程
创建线程使用的闭包:
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
输出:
hi number 1 from the main thread!
hi number 2 from the main thread!
hi number 1 from the spawned thread!
hi number 3 from the main thread!
hi number 2 from the spawned thread!
hi number 4 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
19.2 线程与 move 闭包
如果要把外部的数据出传入到线程,那么就需要使用 move 闭包,会把数据的所有权传递给该线程:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
// v 在这里不能使用了,所有权已传递给线程
handle.join().unwrap();
}
19.3 消息传递
线程间通信采用的信息传递方式,一个生产者(发送),一个消费者(接收),相当于线程间的通道,下面例子采用 mpsc,mpsc 是多个生产者,单个消费者
通道,如:
- 单线程 - [单发送 - 单接收]
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
// val 在这里就不能使用了,所有权在上一步就传递给了send
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
- 单线程 - [多发送 - 多接收]
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
- 多线程 - [多发送 - 多接收]
# use std::thread;
# use std::sync::mpsc;
# use std::time::Duration;
#
# fn main() {
// --snip--
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx);
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
// --snip--
# }
19.4 共享状态并发
多个线程拥有数据的所有权
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
20 面向对象
21 智能指针
22 测试
22.1 模块测试和集成测试
Rust 的测试分为单元测试
和集成测试
,对应的测试代码只能在 cargo test
命令下编译运行;
- 单元测试
单元测试代码一般存在于源码中,测试模块需要用#[cfg(test)]
修饰,此模块中的测试函数需要用#[test]
修饰,如:
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)] //标记该模块为测试模块
mod tests {
use super::*;
#[test] // 标记该函数为测试函数
// #[should_panic] // 如果出错了也不终止程序
// #[ignore] // 测试时忽略该函数
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}
}
- 集成测试
集成测试需要在项目根目录创建tests
文件夹,在该文件下创建测试文件xxx.rs
,并在里面编写测试函数,如:
use adder; // 导入源程序中需要测试的模块
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
22.2 测试代码常用关键字
assert!(xxx); // 如果值为 true,则通过
assert_eq!(xxx, xxx); // 如果两个值相等,则通过
assert_ne!(xxx ,xxx); // 如果两个值不相等,测通过
panic!("...{}", num); // 终止程序并打印信息
也可以用 Result<T, E> 作为函数的返回来编写测试,如:
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
22.3 测试命令
cargo test // 编译运行测试程序
cargo test [函数名] // 测试某个函数,只要函数名包含该字符串的都会被测试
cargo test -- --test-threads=1 // 单线程测试,因为默认情况是多个测试同时进行
cargo test -- --nocapture // 允许显示其他打印信息,比如 println! ,测试通过的也要打印出信息
cargo test -- --ignored // 让代码中用 #[ignore] 修饰过的函数也参与测试
暂时未分类
交换两个变量
std::mem::swap(&mut a, &mut b); // 相当于交换两者的所有权,数据区域的内存地址没变
退出程序
std::process::exit(0);
将一个 String 变成 &str 用 &* 符号
fn use_str(s: &str) {
println!("I am: {}", s);
}
fn main() {
let s = "Hello".to_string();
use_str(&*s);
}
实现链表的例子
pub struct ListNode {
pub val: i32,
pub next: Option<Box<ListNode>>
}
impl ListNode {
fn new(val: i32) -> Self {
ListNode {
next: None,
val
}
}
}
pub fn function(l1: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut res = ListNode::new(0);
let mut res_temp = &mut res;
let mut p_l1 = &l1;
while p_l1.is_some() {
if let Some(node) = p_l1 {
p_l1 = &node.next;
}
res_temp.next = Some(Box::new(ListNode::new(0)));
res_temp = res_temp.next.as_mut().unwrap();
}
res.next
}