rustdesk中继服务器搭建 windows rust服务器直连指令
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:///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
launch.json
{ "version": "0.2.0", "configurations": [ { "name": "Debug", "type": "gdb", "preLaunchTask": "build", "request": "launch", "target": "${workspaceFolder}/target/debug/${workspaceFolderBasename}", "cwd": "${workspaceFolder}" } ] }添加完毕后,即可点击按钮调试;
2 编写、运行rust程序
- 创建 文件
- 编写内容:
- 编译
- 运行
- 查看文档
3 cargo 命令
- 新建工程
- 编译
- 运行
- 检查
``bash
cargo check
4 常用语句
- 获取键盘输入
5 变量与常量
5.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’
- 浮点型
- 布尔型
- 字符型
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 元组和数组
- 元组
元组的元素可以是不同的类型,元组长度固定,一旦声明,其长度不会改变;从元组中取出元素有下面两种方式:
- 数组 array
其元素的类型必须一样:
访问数组时,如果索引超出了数组的范围,编译可以正常通过,但运行时会报这个访问超出范围的错;
- slice
slice 是对数组 array 的切片,所以通过 slice 可以获取 array 的部分或全部的访问权限:
- 函数类型
可以创建变量指向函数:
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); } }- 注释
- 新建变量
- 打印
10 所有权
先解释以下堆和栈,一般而言,数据占用已知且固定大小,就可以把这类数据存放在栈中,如果数据占用的大小未知,而且是在程序中产生,那么这类数据会放在堆中;栈在内存中是连续的内存空间,用进栈、出栈的方式管理,堆的产生需要向系统申请一段足够大的内存空间,位置不连续;访问堆的时间比栈长;
RUST 的所有权主要用于管理内训,所有权有以下几个规则:
- Rust中的每一个值都有一个被称为所有者的变量;
- 值有且只有一个所有者;
- 当所有者(变量)离开作用域,这个值将被丢弃;
10.1 String 类型
这个类型不仅仅存放在栈上,它的值会存放在堆上,所以能够存储在编译时未知大小的文本;相关的一些使用如下:
let mut s = String::from("hello"); s.push_str(", world!"); println!("{}", s);10.2 变量与数据交互的方式
10.2.1 方式一:移动
分析几个例子:
- 移动普通数字变量
在上面的代码中,两个变量的值都为5,因为它们内存大小固定,将存放在栈中,栈里的变量移动时相当于复制;
- 移动String类型
String类型的值存放在堆中,所以应该会有深拷贝和浅拷贝的问题,不过在Rust中,这里s2 = s1更像是浅拷贝,但与浅拷贝不同的是这里s1将会变得无效,同时,s1把所有权交给了s2,比如下面这段代码不能运行:
let s1 = String::from("hello"); let s2 = s1; println!("{}", s1); 换句话说,s1把所有权交给了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, 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 定义结构体
- 2 实例化结构体
- 3 改变成员的值
如果要改变成员的值,那么在实例化结构体的时候就要定义成 mut 类型的,然后就可以在外部更改:
- 4 结构体作为函数参数
- 5 如果新建的实例内容和已经存在的实例有重复的,可以用下面的方法简化代码:
- 6 也可定义与元祖类似的结构体:
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 定义
- 2 作为函数参数
``rust
enum IpAddrKind {
V4,
V6,
}
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);
route(IpAddrKind::V6); - 3 每个成员可以有自己的数据类型
如:
数据类型也可以是结构体:
struct Ipv4Addr { // --snip-- } struct Ipv6Addr { // --snip-- } enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }- 4 也可以用 impl 定义方法
- 5 用 option 解决空值问题
可以这样赋值:
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> 用于函数的返回,处理错误情况
12.2 match
- 1 match 用于过滤枚举的值,逻辑像 swich,哪一个条件匹配了就执行该条件后面的语句:
- 2 枚举还可以嵌套,match 同样可以使用:
- 3 关于 Option<T>,和枚举一样使用:
- 4 match 需要把所有的情况罗列出来,如果不想把所有的罗列出来,可以使用通配符_,如:
如果很多种情况,而只需要操作其中一种情况,就可以用上面的方式解决,代码像这样:
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,文件里的模块等归属在它下面
在 main.rs 文件中调用 fun1函数 和 module1 中的函数:
main.rs
13.2 模块的结构和文件夹的结构关系
再据一个例子来说明其组织管理和文件夹结构的关系,在上面的代码基础上,在 my_lib 中再添加模块 module2:
my_lib.rs
然后在 my_lib.rs 的统计目录下创建 my_lib 文件夹,并在 my_lib 文件夹下创建 module2.rs 文件,并添加代码:
my_lib.rs
调用方式和前面一样:
main.rs
该实验的文件结构:
── src ├── my_lib │ └── module2.rs ├── my_lib.rs └── main.rs13.3 crate、super、use、as关键词
crate 表示根目录
super 表示父目录,也就是父模块
use 使用模块的作用域
as 给作用于提供新名称
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.rsCargo.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:
- 更新 vector :
- 读取 vector 的元素
读取有两种方式:索引法、get方法,如果超出了范围,索引法会在程序运行时报错,而 get 方法会返回 None,在实际运用中考虑这两者的区别;
- 遍历 vector
也可以在遍历的时候改变它的值:
let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }- 在 vector 中存放不同类型的值可以搭配枚举来实现:
14.2 String
- 新建字符串string
- 更新string
- 拼接string
- 索引string
- 遍历string
- string 和 int 互换
- 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
- 利用 vector 生成 HashMap:(其实是 vector -> 元祖 -> HashMap,这两个阶段用到了 zip 和 collect() 方法)
- 也可以用插入 vector 的方法生成 HashMap:
- 访问 HashMap 中的值
可以通过 get 方法并提供对应的键来从哈希 map 中获取值,如果在里面没有对用的值,get 会返回 None:
- 更新 HashMap
1 覆盖一个值
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换:
- 2 只在键没有对应值时插入
如果 HashMap 中不存在这个键值就插入,否则忽略:
- 3 根据旧值更新一个值
下面代码是计数文本中每一个单词分别出现了多少次:
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 函数指针
函数指针可以作为函数的参数和返回值;
作为参数:
作为返回值:
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
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++中的接口;
- 基本使用
- trail 作为参数
此外,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 作为返回值
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 是多个生产者,单个消费者通道,如:
- 单线程 - [单发送 - 单接收]
- 单线程 - [多发送 - 多接收]
- 多线程 - [多发送 - 多接收]
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] 修饰,如:
- 集成测试
集成测试需要在项目根目录创建 tests 文件夹,在该文件下创建测试文件 ,并在里面编写测试函数,如:
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 }