Rust学习

rust学习文档

Rust基础

Hello world

cargo new hello:创建hello工程,cargo工具会自动创建Cargo.toml配置文件和src目录,src中有main.rs文件;

// rust入口函数main
fn main() {
    // println!是一个打印的宏,不是函数;
	println!("Hello, world!");
}

cargo run:运行;运行后会有target目录,其中不含了最后的可执行文件;
cargo build:只编译不运行;
cargo check:校验语法;

Rust变量
// 定义变量是用let关键字
// 定义不可变变量
let a = 1; // 自动推导类型
let b : u32 = 2; // 指定类型为u32
// b = 3; 不可变变量不能赋值,编译错误
println!("a = {}", a);

// 定义可变变量
let mut c = 1;
c = 2;
println!("c = {}", c);

// 变量隐藏
let b : f32 = 1.1; // 将上面的变量b隐藏了;c++中应该会重定义编译错误;
println!("b = {}", b);

// 常量,直接使用const关键字
const MAX_POINT : u32 = 100000;
println!("MAX_POINT = {}", MAX_POINT);
基础数据类型
// bool
let is_true : bool = true;
let is_false = false; // 自动推导
println!("bool var = {}, {}", is_true, is_false); // 结果: bool var = true, false

// char: rust中char是32位的; 因此一个字符可以是汉字或者字母
let a = 'a';
let b = ‘你’;
printlen!("b = {}", b);

// 数字类型:i8, i16, 132, i64, u8, u16, u32, f32, f64
let c: i8 = -111;
println!("c = {}", c);

// 自适应类型:类型长度随着系统不同而不同,isize, usize
println!("max = {}", usize::max_value()); // 打印系统usize的最大数字

// 数组
// 定义方式:[Type; size],中间用分号隔开
let arr : [u32; 5] = [1, 2, 3, 4, 5]; // 用中括号初始化
prinlen!("arr[0] = {}", arr[0]);
// 注意,size也是数组类型的一部分,比如一个函数接收size为三的数组,就不能将上面的arr传进去,编译不通过;
// show(arr); 编译错误,不能将size不为3的数组传入show中;
fn show(arr:[u32; 3]) {
	for i in &arr {
		println!("{}", i);
	}
}

// 元组
let tup: (i32, f32, char) = (-3, 3.69, '好'); // 使用圆括号初始化;
println!("{}, {}, {}", tup.0, tup.1, tup.2); // 使用下标访问
// 拆解元组,分别使用元组元素
let (x, y, x) = tup;
prinfln!("{}, {}, {}", x, y, z);
Rust控制流
// if
let y = 1;
if y == 1 {
	println!("y = 1");
}

// if-else
if y == 1 {
	println!("y = 1");
} else {
	println!("y != 1");
}

// if - else if - else
if y == 1 {
	println!("y = 1");
} else if y == 0 { 
	println!("y = 0");
} else if y == 2 {
	println!("y = 2");
} else {
	println!("other");
}

// let中使用if, if也是有返回值的,
let condition = true;
let x = if condition {
	5 // 不能加分号, 两个分支中必须是同一类型,因为编译时自动类型推导会出错;
} else {
	6
}; // 这时候if-else是表达式,所以需要加分号

// loop : 循环
let mut counter = 0;
loop {
	println!("in loop");
	if counter == 10 {
		break; // 退出循环
	}
	counter += 1;
}
// loop返回值
let res = loop {
	counter += 1;
	if counter == 20 {
		break counter * 2;
	}
}; // 这时候loop是表达式,所以需要加分号;
println!("res = {}", res); // res = 40

// while循环
let mut i = 0;
while i != 10 {
	i += 1;
}
println!("i = {}", i); // i = 10

// for循环
let arr:[u32; 5] = [1, 2, 3, 4, 5];
// 使用数组的迭代器遍历
// for ele in arr.iter() {
// 使用引用遍历数组
for ele in &arr { // 必须加引用,可能for in在定义时就是只接收引用,不能接收值传递
	println!("element = {}", ele);
}
Rust函数
语句:执行一些操作,但是不返回值;
let y = 1; // 语句,不返回值
// let x = (let y = 1); // let y = 1是语句,没有返回值;
表达式:会计算一些值,有返回值
let x = {
	let y = 1;
	y + 1
};
println!("x = {}", x);
使用fn关键字定义函数
// 无参函数
fn other_fun() {
	println!("This is a function");
}

// 有参函数,变量:type
fn oterh_fun1(a: i32, b: u32) {
	println!("a = {}, b= {}", a, b);
}

// 有返回值函数,返回i32类型的值
fn oterh_fun2(a: i32, b: i32) -> i32{
	let result = a + b;
	// 使用return返回
	return result;
	// 直接返回,但是不能有分号
	result
	// 不使用中间变量,常用写法
	a + b
}

fn main() {
	other_fun();
	
	let a: i32 = -1;
	let b: u32 = 2;
	other_fun1(a, b );

	let c: i32 = 9;
	let r = other_fun2(a, c);
	println!("r = {}", r);
}
Rust所有权
1. rust通过所有权机制来管理内存,编译器根据所有权规则对内存进行检查
2. 堆和栈:概念和C++通用;
3. 作用域:{}
4. String内存回收:离开作用域会调用drop方法,类似C++的析构函数,String的结构也和C++类似;
5. 移动(move)
{
    let s1 = String::from("hello");
    println!("s1 = {}", s1);
    let s2 = s1; // 执行move语句,所有权赋给了s2;
    println!("s2 = {}", s2);
    // println!("s1 = {}", s1); // s1已经没有了所有权
    // 问题:如果s2的生命周期比s1短,s2 drop后,所有权会归还s1吗?
}
6. clone trait
{
    let s1 = String::from("hello");
    let s2 = s1.clone(); // clone语句,类似深拷贝
    println!("s2 = {}", s2);
    println!("s1 = {}", s1);
}
7. 栈上数据拷贝:栈上数据执行的都是深拷贝;
// copy trait,copy trait直接调用的是clone,它是一种标记,表明执行的深拷贝,即拷贝之后可以使用原变量;
// 常用的具有copy trait有:所有的整数、浮点型、布尔值、字符类型char、元组
{
	let a = 1;
	let b = a;
	println!("a = {}, b= {}", a, b); // 栈上的数据执行的是深拷贝,
}

8. 函数和作用域
fn func(str: String) {
	println!("{}", str);
}

fn func2(str: String) -> String {
	println!("{}", str);
	str
}

fn main() {
	let s = String::from("hello");
	func(s);
	// println!("{}", s); // s所有权转移了(move语句),离开函数作用域后drop掉了,如果是copy语义就可以继续使用;

	let s1 = String::from(""hello);
	let s2 = func2(s1);
	println!("{}", s2); // 所有权返回给s2了;
}

copy trait和clone trait的区别

  • copy内部没有方法,clone有两个方法;clone trait有clone方法;
  • copy trait是给编译器用的,告诉编译器这个类型默认采用copy语义,而不是move语义;
  • 如果你确实需要Clone trait执行“深拷贝”操作,编译器帮我们提供了一个工具,我们可以在一个类型上添加#[derive(Clone)],来让编译器帮我们自动生成那些重复的代码
Rust引用
fn func(str: &String) -> usize {
	str.len()
}

// 可变引用,借用
fn modify_s(s : &mut String) {
	s.push_str(", world");
}

fn main() {
	// 1. 引用
	let s1 = String::from("hello");
	// let len = func(s1); // 编译不过
	let len = func(&s1); // 需要加引用符&
	println!("len = {}", len);
	println!("s1 = {}", s1); // 传的是s1的引用,所有权没有转移,s1依然可以使用;

	// 2. 取变量引用
	let s2 = &s1; // 取s2为s1引用
	let len2 = func(s2);
	println!("s2 = {}", s2);

	// 3. 借用,可变引用
	let mut s1 = String::from("hello"); // 变量隐藏,可以定义重名的;定义为可变的
	modify_s(&mut s1); // 加mut关键字,借用
	println!("s1 = {}", s1);

	// 4. 在可变引用都定义和访问之间,不能有其他都可变/不可变引用访问
	let mut s1 = String::from("hello");
	let s = &mut s1;
	modify_s(s);
	// println!("s1 = {}", s1); // 在可变引用s最后一次访问之前,不能访问s1或s1都引用;
	println!("s = {}", s);
}
// 5. 悬垂引用
fn dangle() -> &String {
	let s = String::from("hello");
	&s
}

fn main() {
	let ref = dangel(); // 悬垂引用,因为返回的s已经drop了;和C++中悬垂指针/悬垂引用一样;
}
Slice

slice 不是具体的类型,它是一种概念,表示某个数据的存储的一部分值;

fn main () {
	// 1. 字符串slice是String中一部分值的引用;
	let s = String::from("hello world");
	
	// h和w就是一个slice
	let h = &s[0..5]; // ..表示区间,此为左闭右开
	let h = &s[0..=4]; // 左闭右闭
	let h = &s[..=4]; // 开头不写默认是0
	let h = &s[..5]; // 同上
	println!("h = {}", h); // 打印hello

	let w = &s[6..11];
	let w = &s[6..=10];
	let w = &s[6..];
	printlen!("s = {}", w); // 打印world
	let w = &s[..]; // 打印hello world

	// slice的边界必须是完整的utf-8
	let ss = String::from("你好");
	// let w1 = &ss[0..1]; // 编译报错,只取了中文"你"的一半,中文在utf-8中占两个字节;

	// 2. 字面值就是slice,如下s3
	let s3 = "hh"; // 它的类型同时也是&str,不可变引用

	// 3. 其他类型slice
	let a = [1, 2, 3, 4]; // 定义数组a;
	let sss = &a[1..3]; // 定义slice sss
	// println!("sss = {}" sss); // 编译报错
	println!("sss[0] = {}", sss[0]); // 打印2
	println!("sss[1] = {}", sss[1]); // 打印3
	println!("len  = {}", sss.len()); // 打印长度
}
结构体

有三种结构体:具名结构体、元组结构体和单元结构体

fn main() {
	// 1. 定义结构体
	struct User {
		name : String,
		count: String,
		nonce: u64,
		active: bool,
	}

	// 2. 创建结构体
	let mut xiaoming = User {
		name: String::from("xiaoming"),
		count: String::from("80001000");
		nonce: 1000,
		active: true,
	};

	// 3. 修改结构体
	xiaoming.nonce = 20000;

	// 4. 参数名字和字段名字同名的简写方法
	let name = String::from("xiaoxiao");
	let count = String::from("98907987");
	let nonce = 20000;
	let active = false;
	
	// let user1 = User {
	// 	name: name,
	//	count: count,
	//	nonce: nonce,
	//	active: active,
	//};
	// 问题:前面的常量name等在user1定义后,所有权有没有转移;
	// 或者如下方法,可以省略名字
	let user1 = User {
		name,
		coutn,
		nonce,
		active,
	};

	// 5. 从其他结构体创建实例
	// 可以采用一般的创建方法,或者简写
	let user2 = User {
		name: String::from("user2"),
		..user1 // 其他需要从user1赋值的,直接用简写
	};
	println!("name = {}", user2.name);
	println!("nonce = {}", user2.nonce);

	// 6. 元组结构体
	// (1) 没有字段名字
	// (2) 使用圆括号
	stuct Point(i32, i32);
	let a = Point(10, 20);
	let b = Point(30, 11);
	// 访问元组结构体,使用0,1等下标访问
	printtln!("a.0 = {}, a.1={}", a.0, a.1);

	// 7. 没有任何字段的类单元结构体
	struct A{}; // 可以为结构体实现一些函数,类似C++中一个类有成员函数但是没有成员变量

	// 8. 打印结构体,自动推导结构体样式,需要两步:
	// (1) 定义结构体时加上#[derive(debug)]
	// (2) 打印结构体是在大括号内加上:?
	#[derive(debug)]
	struct Person {
		name: String,
		age: u32,
	}
	let person = Person {
		name: String::from("zhangsan"),
		age: 18,
	};
	println!("person = {:?}", person); // 加上:?,打印结果:xiaob = Person {name: "zhangsan", age: 18}
	println!("person = {:#?}", person); // 加上:#?,打印结果会自动换行,如下:
	// person = Person {
	// 		name: "zhangsan",
	// 		age: 18,
	// }
}
Rust方法
#[derive(Debug)] // 这样可以自动推导打印格式
struct Dog {
	name: String,
	weight: f32,
	height: f32,
}

// 实现Dog中的方法
impl Dog {
	// self参数类似C++中成员函数的this参数;  返回字面值引用
	fn get_name(&self) -> &str {
		&(self.name[..])
	}

	fn get_weight(&self) ->f32 {
		self.weight
	}



	// 定义Dog中函数(不加self),类似C++ static成员函数
	fn show() {
		println!("oh oh oh");
	}
}

// 也可以分开实现方法
impl Dog {
	fn get_height(&self) ->f32 {
		self.height
	}
}

fn main() {
	let dog = Dog {
		name: String::from("wangcai");
		weight: 100.0,
		height: 70.5,
	};
	println!("dog = {:#?}", dog); // 使用自动推导来打印结构体;
	println!("name = {}", dog.get_name()); // 调用get_name方法;
	// 调用静态方法
	Dog::show();
}
Rust枚举类型和模式匹配

模式匹配:使用match必须匹配玩值的所有情况;

fn main {
	// 1. 类似C++的定义方式
	enum IpAddrKind {
		V4,
		V6,
	}

	struct IpAddr {
		kind: IpAddrKind,
		address: String,
	}

	let i1 = IpAddr {
		kind: IpAddrKind::V4,
		address: String::from("127.0.0.1"),
	};

	let i2 = IpAddr {
		kind: IpAddrKind::V6,
		address: String::from("::1"),
	};

	// 2. rust语言提倡的定义方式:定义好子类型,如V4(String),并且子类型还可以具有值
	enum IpAddr2 {
		V4(String),
		V6(String),
	};
	let i1 = IpAddr2::V4(String::from("127.0.0.1")); // 子类型IpAddr2::V4,值为"127.0.0.1"
	let i2 = IpAddr2::V6(String::from("::1"));

	// 3. 子类型可以是不同都类型
	// 和C++的枚举不一样,c++中枚举都是一样的类型
	enum Addr3 {
		V4(u8, u8, u8, u8),
		V6(String),
	}
	let i1 = Addr3::V4(127, 0, 0, 0);
	let i2 = Addr3::V6(String::from("::1"));

	
}
// 4. 经典用法
enum Message {
	Quit, // 等同于类单元结构体
	Move{x: i32, y: i32}, // Move是一个结构体
	Write(String), // 元组结构体
	Change(i32, i32, i32),
}
// 5. 枚举类型的方法及匹配机制,必须匹配完所有情况,否则编译出错
impl Message {
	fn prin(&self) {
		match self {
			Message::Quit => println!("Quit"),
			Message::Move{x, y} =>println!("Move x = {}, y = {}", x, y), // Move是结构体,使用花括号
			Message::Change(a, b, c) => println!("Change a = {}, b = {}, c = {}", a, b, c),
			Message::Write(s) => println!("Write s = {}", s),
			_ => println!("Write") // 下划线表示default
		}
	}
}

fn main() {
	let quit = Message::Quit;
	quit.prin();

	let mo = Message::Move{x: 10, y: 20};
	mo.prin();

	let wr = Message::Write(String::from("write"));
	wr.prin();

	let ch = Message::Change(1, 2, 3);
	ch.prin();
}
Option

Option是标准库定义的一个枚举类型;

enum Option<T> {
	Some(T),
	None,
}

使用option的方式:

fn main() {
	let some_num = Some(5);
	let some_str = Some(String::from("hello"));
	let absent_num : Option<i32> = None;

	let x: i32 = 5;
	let y:Option<i32> = Some(3);
	let mut tmp = 0;
	// 取Option中的值;
	match y {
		Some(i) => {tmp = i;},
		None => {println!("Do nothing");},
	}
	let sum = x + tmp;
	println!("sum = {}", sum);

	let res = plus_one(y);
	match res {
		None => {println!("Nothing");},
		Some(i) => {println!("res = {}", i);},
	}

	// 使用if else代替match
	// let Some(value) = plus_one(y)是一个布尔表达式,赋值成功返回true,否则false
	if let Some(value) = plus_one(y) {
		println!("value = {}", value);
	} else {
		println!("Nothing");
	}
	
}

fn plus_one(x: Option<i32>) -> Option<i32> {
	match x {
		None => None,
		Some(i) => Some(i+1),
	}
}
Rust Vector
fn main() {
	// 1. 创建vector
	let  mut v:Vec<i32> = Vec::new();
	v.push(3);

	// 2. 创建vector,并初始化,使用中括号初始化
	let v = vec![1, 2, 3];

	// 3. 丢弃vector,出作用域后自动丢弃
	{
		let v1 = vec![1, 2];
	}

	// 4. 读取元素
	let one: &i32 = &v[0]; // 使用引用
	println!("one = {}", one); // 自动解引用
	println!("one = {}", *one); // 使用*解引用

	// 使用match读取数据,(推荐方法,避免数组越界)
	match v.get(1) {
		Some(value) => println!("value = {}", value),
		_ => println!("Do nothing"),
	}

	// 5. 更新元素
	let mut v2:Vec<i32> = Vec::new();
	v2.push(1);
	v2.push(2);

	// 6. 遍历
	// (1) 不可变的遍历,只读
	for i in &v2 {
		println!("i = {}", i);
	}

	// (2) 可变的遍历,修改
	for i in &mut v2 {
		*i += 1; // 解引用
		println!("i = {}", i);
	}

	// 7. 存储枚举类型值
	enum Context {
		Text(String),
		Float(f32),
		Int(i32),
	};
	let c = vec![
		Context::Text(String::from(hello)),
		Context::Int(1),
		Context::Float(0.001),
	];
	
	// 8. 补充
	let mut v = vec![1, 2, 3, 4];
	let first = &v[0]; // 不可变引用
	v.push(5); // 这里使用了v的可变引用,将导致之前的不可变引用不能再使用
	// println!("first = {}", first);
}
Rust String
// 1. 创建空的字符串
let mut s0 = String::new();
s0.push_str("hello");
println!("s0 = {}", s0);

// 2. 通过字面值创建字符串
let s1 = String::from("hello");
println!("s1 = {}", s1);
let s1 = "hello".to_string();
println!("s1 = {}", s1);

// 3. 更新字符串
// push_str()字面值
let  mut s2 = String::from("hello");
s2.push_str(" world");
println!("s2 = {}", s2);
// push_str()String
let ss = " !".to_string();
s2.push_str(&ss); // 没有拿走ss的所有权,后面仍然可以使用ss
println!("s2 = {}", s2);
println!("ss = {}", ss);
// 使用push(),添加字符(rust中字符是32位)
let mut s2 = String::from("tea");
s2.push('m');
println!("s2 = {}", s2);
// 拼接两个字符串
let s1 = "hello".to_string();
let s2 = String::from(" world");
let s3 = s1 + &s2;
println!("s3 = {}", s3);
// println!("s1 = {}", s1); // 不能使用s1,因为s1的所有权给了s3(相当于把s2拼接到s1上,并把s1的所有权给s3)
println!("s2 = {}", s2); // 可以使用s2
// 使用format!宏拼接字符串
let s341 = String::from("tic");
let s342 = String::from("tac");
let s343 = String::from("toe");
let s344 = format!("{}-{}-{}", s341, s342, s343);
println!("s344 = {}", s344); // 输出tic-tac-toe
println!("s341 = {}", s341); // 仍然可以使用s341/s342/s343;

// 4. String不支持索引,因为rust中String使用的是utf-8,索引不一定能取到某个字符边界,比如中文;
let s4 = String::from("hello");
println!("h4.len = {}", s4.len()); // 输出5;
let s4 = String::from("你好"); 
println!("h4.len = {}", s4.len()); // 输出6;
// let res = s4[0]; // String不能被索引

// 5. str索引
let s5 = "你好";
let h5 = &s5[0..3]; // 取slice;
println!("h5 = {}", h5); // 输出“你”
// let h6 = &s5[0..2]; // 2不是字符边界,不能编译通过

// 6. 遍历
// 使用chars,取出的是完整的utf-8字符,比如一个完整的汉字;
for c in s4.chars() {
	println!("c = {}", c); // 依次打印“你”和"好"; 
}
// bytes
for b in s4.bytes() {
	println!("b = {}", b); // 打印出六个字节,b=228等
}
HashMap
// 1. HashMap<K, V>: 存储kv数据
// 2. 创建HashMap
use std::collections::HashMap; // 需要先导入HashMap的包
fn main() {
	let mut scores : HashMap<String, i32> = HashMap::new();
	scores.insert(String::from("Blue"), 10); // 使用insert添加值
	scores.insert(String::from("Red"), 20);
	// 通过vectore创建HashMap
	let keys = vec![String::from("Blue"), String::from("Red")];
	let values = vec![10, 20];
	let scores : HashMap<_, _> = keys.iter().zip(values.iter()).collect(); // _是占位符
}
// 3. 读取
// 读取2中的sores
let k = String::from("Blue");
if let Some(v) = scores.get(&k) { // get()返回的是option
	println!("v = {}", 10); // v = 10
}

// 4. 遍历:会以任意的顺序遍历出来
for (key, value) in &scores {
	println!("{}, {}", key, value);
}

// 5. 更新
let mut ss = HashMap::new();
// 使用insert直接插入;
ss.insert(String::from("one"), 1); 
ss.insert(String::from("two"), 2);
ss.insert(String::from("one"), 3); // 会覆盖掉前面插入的;
// 键不存在的时候才插入
let mut ss1 = HashMap::new();
ss1.insert(String::from("one"), 1); 
ss1.insert(String::from("two"), 2);
ss1.entry((String::from("one")).or_insert(3); // entry判断是否存在,or_insert插入
println!("ss1 = {:?}", ss1);
// 根据旧值来更新一个值
let text = "hello world wonderful world";
let mut m = HashMap::new(); // 用于存储text中单词的计数
for word in text.split_whitespace() { // split_whitespace()使用空格分割
	let count = map.entry(word).or_insert(0); // count是value的指针
	*count += 1; // 更新value
}
println!("m = {:?}", m);
包、模块

包、crate、模块等定义见https://www.rust-lang.org/zh-CN/learn文档;

// 创建模块
// 模块:用于控制作用域和私有性,默认是私有的;
mod factory { // mod关键字定义一个模块
	pub mod produce_refrigerator { // pub表示public
		pub fn produce_re() {
			println!("produce regrigerator!");
		}
	}
	pub mod produce_washing_machine {
		pub fn produce_washing_maching() {
			println!("produce washing_maching!");
		}
	}
}

fn main() {
	factory::produce_regrigerator::produce_re();
}

创建lib库:“cargo new --lib mylib”;src目录里面自动生成lib.rs;在mylib中实现的模块需要在lib.rs中声明;
在mylib中创建mylib文件夹,mylib文件夹中创建factory.rs,同时factory也是模块名

pub mod produce_refrigerator { // pub表示public
	pub fn produce_re() {
		println!("produce regrigerator!");
	}
}
pub mod produce_washing_machine {
	pub fn produce_washing_maching() {
		println!("produce washing_maching!");
	}
}

在mylib库下src/lib.rs中声明模块:模块名和factory.rs的文件名一样

pub mod factory;

依赖模块:需要修改Cargo.toml配置依赖的其他模块,修改如下(path的路径是当前目录是因为创建的mylib是在当前工程下创建的子工程):

[dependencies]
mylib = {path = "./mylib"}

使用模块:两种方式,绝对路径和使用use导入(推荐use导入时路径到前一级模块)

use mylib::factory::produce_refrigerator;
use mylib::factory::produce_refrigerator::produce_re; // 不推荐直接到最后一级,可能和其他模块重名
use mylib::factory::produce_refrigerator as A; // 使用as关键字,给path别名
use mylib::factory::*; // 导入所有模块,包括produce_refrigerator和produce_washing_machine
fn main() {
	mylib::factory::produce_refrigerator::produce_re(); // 使用的绝对路径,可以不使用use;mylib是库名,factory和produce_refrigerator是父子模块,produce_re函数
	produce_refrigerator::produce_re(); // 使用use相对路径
	produce_re(); // 使用use mylib::factory::produce_refrigerator::produce_re,但是如果多个模块下有函数重名,将有问题;
	A::produce_re();
}

使用外部的lib库:
在Cargo.toml中导入依赖:添加库的名字以及版本

[dependencies]
rust-crypto = "0.2"

使用库:直接cargo run下面程序,cargo会自动下载crypto模块

extern crate crypto; // 表示外部库

use crypto::digest::Digest; // 导入库中的Digest
use crypto::Sha3;
fn main() {
	let mut hasher = Sha3::sha3_256();
	hasher.input_str("hello world");
	let result = hasher.result_str(); // 对hello world字符串求hash
	println!("hash = {}", result);
}
Rust错误处理
  1. 分为可恢复错误和不可恢复错误;
    1)可恢复错误通过代表向用户报告的错误和重试操作是合理的情况,例如未找到文件。rust中使用Result<T, E>来实现;
    2)不可恢复错误表示一个bug,如数组越界等,通常程序不应该继续运行了。rust通过panic!来实现;
fn main() {
	panic!("crash here"); // 程序会直接crash
}
  1. BACKTRACE,打印出crash的栈,帮助调试
// 运行cargo run前面加上RUST_BACKTRACE=1,可以帮组打印错误信息(只要是非0数字就行),默认是0,
RUST_BACKTRACE=1 cargo run
  1. Result<T, E>
// 定义
enum Result<T, E> {
	Ok(T),
	Err(E),
}

使用Result

use std::fs::File;
fn main() {
	let f = File::open("hello.txt"); // 返回的f就是result结构
	let r = match f {
		Ok(flie) => file,
		Err(error) => panic!("error: {:?}", error);
	}

	let f = File::open("hello.txt").unwrap(); // 如果是Ok,则会返回file
	let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

传播错误

use std::io;
use std::io::Read;
use std::fs::File;
fn main() {
	let r = read_username_from_file();
	match r {
		Ok(s) => println!("s = {}", s);
		Err(e) => println!("err = {?:}", e);
	};
}

fn read_username_from_file() -> Result<String, io::Error> {
	let f = File::open("hello.txt");
	let mut f = match f {
		Ok(file) => flie,
		Err(error) => return Err(error),
	};
	let f = File::open("hello.txt")?; // 可以使用问号代替上面的match,如果有错误,直接就返回了error;

	let mut s = String::new();
	match f.read_to_string(&mut s) { // match的返回值就是函数的返回值
		Ok(_) => Ok(s), // Ok(s)作为返回值
		Err(error) => Err(error),
	}
}
测试

使用rust自带的测试工具;
创建lib库时,会在lib.rs中自动生成单元测试模块:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

运行测试:

cargo test

Rust进阶

泛型

// 1. 结构体中泛型
#[derive(Debug)]
struct Point<T, U> {
	x: T,
	y: U,
}

// 2. 函数中泛型
fn largest<T: PartialOld + Copy> (list: &[T]) -> T {
	let mut T large = list[0];
	for &item in list.iter() {
		if item > large {
			large = item;
		}
	}
	large
}

// 3. 方法中泛型
// 为上面point实现get方法
impl<T, U> Point<T, U> {
    fn get_x(&self) -> &T {
        &self.x                                                                                                                                        
    }   
    fn get_y(self) -> U { 
        self.y
    }   
}
// 为point实现create新point方法;输入为其他泛型类型的point
// V, W是输入other的泛型,输出泛型是self和other的结合:<T, W>
impl<T, U> Point<T, U> {
    fn create<V, W> (self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y
        }
    }   
}


fn main() {
	let num_list = vec![1, 2, 3, 4];
	let max = largest(&num_list);
	print!("largest num = {}", max);

    let p1: Point<char, char> = Point{x:'a', y:'b'};
    let p2 = Point{x: 1, y: 2}; 
    let p3 = p1.create(p2);

    println!("{:?}", p3);
    println!("Hello, world!");
}

trait

类似接口类,trait中函数有默认实现;

// 定义一个trait
pub trait GetInfo {
	fn get_x(&self) -> &u32;
	fn get_y(&self) ->u32;
}
// 定义一个struct
pub struct Point {
	pub x: u32,
	pub y: u32,
}
// 为Point实现GetInfo trait
impl GetInfo for Point {
	fn get_x(&self) -> &u32 {
		&self.x
	}
	fn get_y(&self) ->u32 {
		&self.y
	}
}
// trait作为参数
pub fn print_info_x(item: impl GetInfo) {
	print!("{}", item.get_x());
}
// trait作为返回值,返回的Point实现了trait GetInfo 
pub fn get_obj() -> impl GetInfo {
	Point{
		x: 2,
		y: 3,
	}
}
// trait_bound
// 写法一
// T必须实现GetInfo和GetName两个trait
pub fn print_info_x<T: GetInfo+GetName>(item: T) {
	print!("{}", item.get_x());
}
// 写法二
pub fn print_info_x<T>(item: T) where T: GetInfo+GetName{
	print!("{}", item.get_x());
}

// 注意:返回值为trait时,函数中不能返回两种类型,即使这两种类型都实现了该trait,类似于C++中返回值是基类指针,返回两种以上子类对象

// 为trait实现trait
// 为实现了GetInfo的类型实现GetName,有点像多重继承
impl<T: GetInfo> GetName for T {
	......
}

生命周期

  1. rust中每一个引用都有生命周期,也就是引用保持有效的作用域(如使用{}为一个作用域),大部分时候生命周期可以隐式的推断
  2. 生命周期的主要目标是避免悬垂引用
  3. rust编译器使用借用检查器来检查生命周期是否有效
// 以下编译不过,r(`a)的生命周期大于了引用对象x(`b)的生命周期
fn main() {
	let r;                                   // ----------------------------------------------`a
	{                                        //
		let x = 5;                           //-------------+---------`b
		r = &x;                              //              
	}                                        //-----------------------`b
	println!("r = {}", r);                   //     
}                                            //-----------------------------------------------`a
函数中的生命周期
// 返回值是入参的引用
// 需要对入参和返回值显示标明生命周期标识符,并不是所有入参都需要标明生命周期,看入参与返回值是否有关
// 需要先在longest后面声明引用标识符(`a),后面才能使用该生命周期
fn longest<`a>(x: &`a str, y: &`a str) -> &`a str {
	if x.len() > y.len() {
		x
	} else {
	 	y
	}
}

// error: r的所有权属于这个函数(局部变量)
fn a_str<`a>(x: &`a str, y: &`a str) -> &`a str {
	let r = String::from("abc");
	r.as_str()
}

fn main() {
	let s1 = String::from("abcde");
	let s2 = String::from("ab");
	let r = longest(s1.as_str(), s2.as_str());
	println!("r = {}", r);
}
结构体中的生命周期
// 结构体中成员变量是引用,需要生命周期标识符
// 同样,需要先在A后面声明生命周期标识符,标明引用类型的成员name的生命周期
#[derive(Debug)]
struct A<`a> {
	name: &`a str,
}

fn main() {
	let n = String::from("hello");
	let a = A{name: &n};
	println!("a = {:#?}", a);
}
生命周期标注的省略

遵守生命周期省略规则的情况下能明确变量的生命周期,则无需显示指定生命周期;函数或者方法的参数的生命周期成为输入生命周期,返回值的生命周期成为输出生命周期。
生命周期的注解省略规则适用于fn以及impl块定义,三条可以自动推断生命周期的规则如下:

  1. 每个引用参数都有它自己的生命周期参数:
    1)一个引用参数的函数,其中有一个生命周期:fn foo<`a>(x: &`a i32)
    1. 多个引用参数的函数,则有多个生命周期:fn foo<`a, `b>(x: &`a i32, y: &`b i32)
  2. 如果只有一个输入生命周期参数,那么输入的生命周期被赋予所有输出声明周期参数:
    fn foo(x: &i32) -> &i32 等价于 fn foo<`a>(x: &`a i32) -> &`a i32
  3. 如果方法有多个输入生命周期,不过其中之一因为方法的缘故为&self或者&mut self,那么self的生命周期被赋予所有输出生命周期
    fn func(&self, x: &str, y: &str) -> &str
// 只有一个输入,那么输入的生命周期被赋予了输出
fn get_s_str(s: &str) -> &str {
	s
} 
fn main() {
	let s = String::from("hello");
	let ss = get_s_str(s.as_str());
	println!("ss = {}", ss);
}
方法中的生命周期
struct StuA<`a> {
	name: &`a str,
}

// impl后面的`a是声明一个声明周期,随后在StuA后面使用改生命周期标注
impl<`a> StuA<`a> {
	fn do_something(&self) -> i32 {
		3
	}
	// 等价于:fn do_something2<`a>(&`b self, s: &str) -> &`a str
	fn do_something2(&self, s: &str) -> &str {
		self.name
	}
	// 因为返回值是s,因此需要显式标明s的生命周期,否则编译提示返回值和输入不匹配
	fn do_something3<`b>(&self, s: &`b str) -> &`b str {
		s
	}
}

fn main() {
	let s = String::from("hello");
	let a = StuA{name: &s};
	println!("{}", a.do_something());
	let s2 = String::from("hello");
	println!("{}", a.do_something2(&s2)); // 返回的是a的name
}
静态生命周期

定义方式:'static
静态生命周期存活于整个程序期间;

let s: &'static str = "hello";

闭包

闭包是可以保持进变量或者作为参数传递给其他函数的匿名函数(C++中lambda表达式)。闭包和函数的不同的是:1)闭包允许捕获调用者作用域中的值;2)闭包的定义可以省略参数和返回值的类型,编译器可以自动推导类型,但是只能自动推导一次;

闭包的定义及使用
// 函数格式:
fn add_one_v1(x: u32) -> u32 {
	x + 1;
}
fn main() {
	// 1. 多种定义闭包格式
	// 与函数add_one_v1等价的闭包格式:
	let add_one_v2 = |x: u32| -> u32 {x + 1;};
	// 可以为每个参数和返回值自动推导一个具体的类型,但不能推导两次
	let add_one_v3 = |x| {x + 1};
	// 简单的函数体可以不用加花括号
	let add_one_v4 = |x| x + 1;

	// 2. 调用函数
	let a = add_one_v1(5);
	// 调用闭包
	let b = add_one_v2(5);
	let c = add_one_v3(5);
	let d = add_one_v4(5);

	// 错误使用:不能推导类型两次的例子,和模板还是有区别的
	let example_closure = |x| x;
	let s = example_closure(String::from("hello"));
	println!("s = {}", s);
	// 错误,第一次已经推导类型为String了,因此入参不能为5
	let n = example_closure(5);
	// 正确,多次使用是同样类型
	let n = example_closure(5.to_string());
}
捕获作用域中变量

闭包可以通过三种方式捕获起环境,对应着函数的三种获取参数的方式,分别是获取所有权、可变借用和不可变借用;
这三种捕获方式被编码为如下三个Fn Trait:
1) FnOnce,捕获环境中变量,并获取其所有权;名称中Once部分代表了闭包不能多次获取相同变量的所有权(即该闭包不能多次调用);
2) FnMut,获取可变的借用值,可以改变环境中变量;当匿名函数体里改变了环境变量的值的时候,匿名函数就是FnMut;
3)Fn,从环境中获取不可变借用
定义闭包后,编译器会根据使用环境变量的方式来自动的推导闭包实现了哪些Fn Trait,所有闭包都实现了FnOnce,因为所有闭包都至少可以被调用一次;如对于下面例子中的闭包|x| x+i,则自动实现了不可变借用Fn;也可以显示的使用move来指定闭包获取环境变量的所有权(在使用move定义闭包时,如果变量具有copy语义,则变量执行拷贝,如果变量是move语义,则转移所有权);

fn main() {
	let i = 1;
	// 定义闭包,并捕获闭包环境中变量i
	let exe = |x| x + i;
	// 调用闭包
	let r = exe(5);
	println!("r = {}", r)

	// 使用move定义闭包获取所有权;
	let x = vec![1, 2, 3]; // vector是move语义
	let equal_to_x = move |z| z==x; // 闭包定义后x的所有权就转移了;
	println!("{?:}", x); // 不能编译通过,因为x所有权已经转移了;
	assert!(equal_to_x(y));
}
带泛型和Fn trait的闭包

闭包可以作为Fn被存储起来,例如作为回调函数,类似于C++中function模板

// 泛型T必须实现Fn trait,T可以作为函数调用
struct Cacher<T> 
		where T: Fn(u32) -> u32 {
	calcuation: T, // 可以作为回调函数调用
	value: u32,
}

impl<T> Cacher<T> 
		where T: Fn(u32) -> u32 {
	fn new(calcu: T, i: u32) -> Cacher<T> {
		Cacher {
			calcu, // 结构体省略变量名的初始化方法
			value: i,
		}
	}
	fn Run(&self) -> u32 {
		calcuation(value) // 调用回调函数
	}
}

fn main() {
	let cacher = Cacher::new(|x| x+1);
	let x = cacher.Run();
	println!("x = {}", x);
}

迭代器

  1. 迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑;
  2. 迭代器是惰性的,在调用方法使用迭代器之前,不会有任何效果;
  3. 每个迭代器都实现了iterator trait,iterator trait定义在标准库中;
标准迭代器

next是Iterator被要求实现的唯一的方法(即所有的迭代器都必须实现next方法),next一次返回一个元素,当迭代器结束时,返回None;
标准库迭代器中实现了多种方法,可以分为如下几种:

  1. 消费适配器(consuming adaptors):这些方法会调用next方法;之所以叫消费适配器,因为他们调用next方法会消费迭代器;一个消费适配器的例子是 sum 方法。这个方法获取迭代器的所有权并反复调用 next 来遍历迭代器;
  2. 迭代器适配器(iterator adaptors):他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果;
trait Iterator {
	type Item; // trait关联类型,表示实现改trait的结构体需要指定该Item类型
	fn next(&mut self) -> Option<Self::Item>; // type Item和self::Item这种用法叫做定义trait的关联类型
}

fn main() {
	let v1 = vec![1, 2, 3];
	let v1_iter = v1.iter(); // for循环无需let mut,因为for循环获取了v1_iter的所有权,并将其置为可变
	for val in v1_iter {
		println!("val = {}", val)
	}

	let mut v1_iter = v1.iter(); // next方法定义中是mut self,所以需要let mut
	if let Some(v) = v1_iter.next() {
		println!("v = {}", v);
	} else {
		println!("At end");
	}

	// -------迭代可变引用-------
	let mut v2 = vec![1, 2, 3];
	let mut v2_iter = v2.iter_mut(); // v2.iter_mut(),迭代可变引用
	if let Some(v) = v2_iter.next() {
		*v = 3; // 加*解引用,对引用对象进行赋值
	}
	println!("v2 = {?:}", v2); // 打印3,2,3

	// --------消费适配器--------
	let v1 = vec![1, 2, 3];
	let v1_iter = v1.iter();
	let total: i32 = v1_iter.sum(); // 调用消费适配器sum来求和

	// --------迭代器适配器---------
	let v1 = vec![1, 2, 3];
	// v1.iter().map(|x| x+1);创建出一个新的迭代器,它并不会做任何事,因为迭代器是惰性的,除非调用了消费适配器;
	let v2: Vec<> = v1.iter().map(|x| x + 1).collect(); // map: 对每一个元素执行闭包
	println!("v2 = {?:}", v2);
	let v3: Vec<> = v1.into_iter().filter(|x| *x > 5).collect();
	println!("v3 = {?:}", v3);
}
自定义迭代器
struct Counter {
	count: u32
}
impl Counter {
	fn new() -> Counter {
		Counter {count: 0}
	}
}

impl Iterator for Counter {
	type Item = u32; // 指定关联类型
	fn next(&mut self) -> Option<Self::Item> {
		self.count += 1;
		if self.count < 6 {
			Some(self.count)
		} else {
			None
		}
	}
}

fn main() {
	let mut counter = Counter::new();
	for i in (0..6) {
		if let Some(v) = counter.next() {
			println!("i = {}, v = {}", i, v);
		} else {
			println!("i = {}, at end", i);
			break;
		}
	}
}

Cargo

Cargo自定义构建

cargo run:编译并运行,会生成target目录,里面包含debug版本的bin文件
cargo run --release:编译并运行,生成的release版本
cargo build:编译,也会生成target目录,生成debug版本
cargo build --release:编译生成release版本的bin文件

配置优化级别(配置Cargo.toml文件),分别配置debug和release版本的优化级别;下列配置为默认的优化级别,不配置也可以

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3 
使用别人的包(crate)
  1. 修改配置文件,导入包的依赖
[dependencies]
rust-crypto = "0.2"
  1. 使用use导入相应的包
use crypto::digest::Digest;
use crypto::sha3::Sha3;

fn main() {
	let mut hasher = Sha3::sha3_256();
	hasher.input_str("Hello World");
	let result = hasher.result_str();
	println!("result : {}", result);
}

cargo run:此时会自动从crates.io导入crypto依赖的包,有点像java maven中配置依赖后自动导入依赖包

文档注释
  1. 使用/////!进行文档注释,///对函数进行文档注释,//!对crate进行文档注释,可以多行注释,支持Markdown语法
  2. 运行cargo doc,会在target文件夹中生成doc文件夹
  3. 运行cargo doc --open,生成doc并在浏览器中打开一个网页,显示自动生成的文档
/// 给一个数加一
/// #Example
/// .......
pub fn add_one(x: i32) -> i32 {
	x + 1
}
crate发布与撤回
  1. 创建crate.io账号:通过GitHub账户注册,并通过cargo login *****来登录
  2. 发布前需要在Cargo.toml中增加描述,如下
[package]
name = "package name"
version = "0.1.0"
license = "MIT" # 可以在Linux基金会的SPDX查看可以使用的标识
authors = ["zhangsan"]
description = "test crate"
  1. 发布crate:运行cargo publish进行发布
  2. 撤回指定版本:cargo yank --vers 0.1.0,撤回0.1.0版本
cargo工作空间

为project或者lib设定工作空间,在构建大型工程时会经常使用;
新建Cargo.toml文件,并为adder和add-one设置当前目录为工作空间:

[workspace]
members = [
	"adder",
	"add-one",
]

可以在当前目录直接运行cargo new / cargo build等命令来操作响应项目;
当前工作空间有多个项目,指定运行的项目:cargo run -p adder

智能指针

  1. Rust智能指针概念和C++中智能指针类似;
  2. 智能指针和普通引用的区别:引用只是借用数据的指针,而智能指针则拥有它们指向的数据;问题:引用不能不能和借用(可变引用)同时在一起,因为借用具有数据的写权限,那么多个智能指针可以同时拥有写权限吗?
    智能指针通常使用结构体实现,智能指针区别于常规结构体的显著特征在于其实现了Deref和Drop trait;
    • Deref trait:解引用,允许智能指针结构体实例表现的像引用一样,这样可以编写即用于引用,又用于智能指针的代码;
    • Drop trait:类似C++析构函数,允许自定义当智能指针离开作用域时的操作;
  3. 标准库中的智能指针:
    Box:用于在堆上分配
    Rc:一个引用计数类型,其数据可以有多个所有者;
    Ref和RefMut:通过RefCell访问,一个运行时而不是在编译时执行借用规则的类型;
Box及使用

有点类似c++ unique_ptr

  1. 最简单的智能指针是Box,其类型为Box。Box允许将值放在堆上,Box只保留了只向堆上数据的指针,除了数据被存储在堆上外,Box没有任何性能损失;
  2. Box使用如下场景:
    • 当有一个在编译时未知大小的类型,而又需要在确切大小的上下文中使用这个类型值的时候;(如:在一个list环境下存放数据,但是每个元素的大小在编译时又不确定)
    • 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候;
    • 当希望拥有一个值并只关心它的类型是否实现了特定trait而不是其具体类型时;
fn main() {
	let b = Box::new(5);
	println!("b = {}", b);
}

使用Box

/*
// 定义一个链表List:这个链表是连续存储的,编译报错,因为List的类型大小不确定,在栈上定义变量大小需要确定;
enum List {
	Cons(i32, List), // Cons是List的一个变量,它是一个匿名结构体,包含两个元素,第一个是i32,第二个是List
	Nil,
}
// 等价的C++中定义:
struct List { // 栈内存大小不固定,编译报错
	int value,
	struct List l,
}
*/
// 使用Box定义List,编译通过,因为Box在栈上只占用一个指针的内存,是确定的
// 将Box当做一个指针来使用
enum List {
	Cons(i32, Box<List>),
	Nil,
}

fn main() {
	use List::Cons;
	use List::Nil;
	let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
解引用

实现Deref trait允许我们重载解引用运算符;

/*
let a: A = A::new(); // 可以使用解引用的前提是类型A实现了Deref trait
let b = &a; // b为a的引用
let c = *b; // 解引用
*/
fn main() {
	let x = 5;
	let y = &5;
	assert_eq!(5, x);
	assert_eq!(5, *y);

	let z = Box::new(x);
	assert_eq!(5, *z);
}

例子:实现MyBox,并实现Deref trait

use std::ops::Deref;
struct MyBox<T>(T);

impl<T> MyBox<T> {
	fn new(x: T) -> MyBox<T>{
		MyBox(x)
	}
}

// 注意:实际调用*MyBox的时候,返回值的类型是T
impl<T> Deref for MyBox<T> {
	type Target = T;
	fn deref(&self) -> &T {
		&self.0
	}
}

fn main() {
	let x = 5;
	let y = MyBox::new(x);
	assert_eq!(5, x);
	assert_eq!(5, *y);
}

解引用多态与可变性交互:T解引用为U,当传入&T时但是参数是&U时,自动将&T变为&U;类似&&T变为&T的感觉,但是&T不能自动解引用为T;

  1. 当T: Deref<Target=U>时, 从&T到&U
  2. 当T: DerefMut<Target=U>时,从&mut T到&mut U
  3. 当T: Deref<Target=U>时,从&mut T到&U
fn main() {
	let m = MyBox::new(String::from("Rust"));
	hello(&m); // 将MyBox变为&String,再将String的解引用变为字符串slice
}

fn hello(name: &str) {
	println!("Hello, {}", name);
}
Drop trait

数据结构离开作用域执行,类似析构函数;
rust提供了std::mem::drop来提前释放数据结构;

struct Dog {
	name: String,
	count: i32,
}

impl Drop for Dog {
	fn drop (&mut self) { // 必须时mut,因为资源释放需要修改对象
		println!("{} leave", self.name);
		// self.count -= 1;
	}
}
fn main () {
	let a = Dog{name: String::from("wangcai")};
	{
		let b = Dog{name: String::from("dahuang")};
		println!("befor b leave");
	}

	// 提前释放a对象;
	// a.drop(); drop方法不能显示的调用;
	drop(a); // 调用std::mem::drop提前析构对象a; 
	println!("hello world");
}
Rc智能指针

引用计数智能指针,类似C++ shared_ptr;
通过Rc共享的数据只允许程序只读的共享;

// 实现可共享的链表
enum List {
	// Cons(i32, Box<List>)
	Cons(i32, Rc<List>), // 使用Rc,因为Box不能共享
	Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
	let a = Cons(1, Rc::new(Cons(2, Rc::new(Cons(3, Rc::new(Nil)))))); // a中所有节点都是Rc对象(智能指针)
	// let b = Cons(2, Box::new(a)); // 如果是Box,会执行copy或者move,List会执行move从而转移了所有权,在c构造时就会编译错误;
	let b = Cons(2, Rc::clone(&a)); // 将a拷贝一份,相当于将a中所有Rc指针节点拷贝一份,但是b和a会共用同一份堆中数据;当然,b比a会对一个节点,值为2;
	// let b = Cons(2. a.clone()); 这种写法也可以,应该是自动为Cons对象添加了clone方法;
	let c = Cons(3, Rc::clone(&a));
}
RefCell智能指针
  1. 内部可变性:需要对不可变引用进行改变数据
  2. RefCell在运行时检查借用规则,RefCell<T>代表数据的唯一所有权;
  3. RefCell<T>类似Rc<T>,RefCell<T>不是线程安全的,只适用于单线程;
  4. Rc/Box/RefCell区别
    Rc有多个所有者,Box和RefCell只有单一所有者
    Box在编译时执行可变或者不可变检查,Rc在编译时执行不可变检查,RefCell在运行时执行可变或不可变检查;
    因此RefCell本身即是时不可变的,也可以修改其内部的值;
// 实现可共享的链表
#[derive(Debug)]
enum List {
	Cons(Rc<RefCell<i32>>, Rc<List>), 
	Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
	let value = Rc::new(RefCell::new(5));
	let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
	let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
	let c = Cons(Rc::new(RefCell::new(7)), Rc::clone(&a));
	
	println!("a before : {:?}", a);
	println!("b before : {:?}", b);
	println!("b before : {:?}", b);

	// 修改不可变value中的值;
	*value.borrow_mut() += 10;
	
	println!("a after : {:?}", a);
	println!("b after : {:?}", b);
	println!("c after : {:?}", c);
}
引用循环

使用Rc引用,a引用了b, b引用了a,在打印时就会无限打印;可以使用上面的List作为例子,打印一个循环引用最后会栈溢出;

弱引用

弱引用Weak<T>,可以解决循环引用的问题;弱引用不会增加强引用的引用计数;

  1. 弱引用通过Rc::downgrade获取弱引用;
#[derive(Debug)]
enum List {
	Cons(i32, RefCell<Weak<List>>), 
	Nil,
}

impl List {
	fn tail(&self) -> Option<&RefCell<Weak<List>>> {
		match self {
			Cons(_, item) => Some(item),
			Nil => None,
		}
	}
}

use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
use std::rc::Weak;
fn main() {
	// 创建两个节点
	let a = Rc::new(Cons(5, RefCell::new(Weak::new())));
	let a = Rc::new(Cons(6, RefCell::new(Weak::new())));

	// b指向a
	if let Some(link) = b.tail() {
		  *link.borrow_mut() = Rc::downgrade(&a);
	}

	// a指向b
	if let Some(link) = a.tail() {
		  *link.borrow_mut() = Rc::downgrade(&b);
	}
	println!("a tail:{}", a.tail());
}

多线程

线程与move闭包
use std::thread;

fn main() {
	let v = vec![1, 2, 3];
	let handle = thread::spawn(move ||
		{println!("v:{:?}", v);
	});

	handle.join().unwrap();
}
多线程

使用std::thread创建线程,可以传入闭包或者函数;
rust提供1::1线程模型,即一个rust线程对应一个OS线程;

use std::thread;
// 主线程中启动子线程,并等待子线程结束;
fn main() {
	let th = thread::spawn(|| {
		for i in 1..10 {
			println!("number in spawn {}", i);
			thread::sleep(Duration::from_millis(1));
		}
	});
	
	for i in 1..5 {
		println!("number in main {}", i);
		thread::sleep(Duration::from_millis(1));
	}
	th.join().unwrap();
}
通道
  1. rust中实现消息传递并发的主要工具时通道。通道由两部分组成,一个时发送端,一个时接收端;发送或接收任一被丢弃则认为通道关闭;
  2. 通道创建
    1)通过mpsc::channel创建,多生产者,单消费者;
    2)通过spmc::channel创建,单生产者,多消费者;
    3)创建通道后,返回的是发送者和接受者;
    let (tx, rx) = mpsc::channel();
  3. 传递数据:
    1. 发送者send返回的是一个Result<T, E>类型;如果接收端已经被丢弃了,则会返回错误;
    2. 接受者recv返回的也是Result<T, E>类型;如果发送端已经被丢弃了,则会返回错误;
    3. recv会阻塞,直到一个消息到来;try_recv()不会阻塞;
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(); // 发送消息;
	});

	let r_val = rx.recv().unwrap(); // 主线程接收数据;recv时阻塞的,所以不同join子线程;
	println!("Got {}", r_val);
}
多发送者
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

fn main() {
	let (tx, rx) = mpsc::channel();
	let tx1 = mpsc::Sender::clone(&tx);

	thread::spawn(move || {
		let val = String::from("th");
		tx.send(val).unwrap();
	});
	thread::spawn(move || {
		let val = String::from("th1");
		tx1.send(val).unwrap();
	});

	// 主线程接收数据
	for rec in rx {
		println!("recevie {}", rec);
	}
}
互斥器

通道类似于单所有权,当值通过通道传递后,发送者无法使用这个值;
共享内存类似于多所有权,多个线程可以访问同一内存位置;
互斥器:Mutex,任意时刻只能有一个线程访问数据;互斥器使用时,需要先获得锁,使用完成后,需要释放锁;
Mutex是一个只能指针,lock方法返回一个MutexGuard的智能指针,内部提供drop方法,当MutexGuard离开作用域时自动释放锁;

use std::sync::Mutex;
fn main() {
	let m = Mutex::new(5); // Mutex<i32>
	{
		let mut num = m.lock().unwrap(); // 获取锁,并获取互斥器中的值;
		*num = 6; // 修改值
	} // 锁离开作用域时自动释放
	println!("m = {:?}", m);
}

多线程使用互斥器,使用Arc包裹Mutex;
Arc,线程安全的共享指针,Rc不是线程安全的,要将对象传递给另一个线程,需要使用Arc

use std::sync::Mutex;
use std::thread;
use std::sync::Arc;

fn main() {
	let counter = Arc::new(Mutex::new(0));

	let mut handles = vec![];

	for _ in 0..10 {
		let cnt = Arc::clone(&counter);
		let h = thread::spawn(move || {
			let mut num = cnt.lock().unwrap();
			*num += 1;
		});
		handles.push(h);
	}

	for h in handles {
		h.join().unwrap();
	}
	println!("counter is {}", *counter.lock().unwrap());
}

RefCell \ Rc与Mutex \ Arc

  1. Mutex也提供内部可变性,与RefCell类似;
  2. RefCell\Rc不是线程安全的,而Mutex\Arc时线程安全的;
Send和Sync trait
  1. rust中有两个内嵌的并发概念:std::marker中的Send和Sync trait;
  2. Send trait表明类型可以在线程间转移所有权;几乎所有rust类型都是Send的,除Rc<T>是不能Send的以外;任何完全由Send类型组成的类型也自动是Send的( 所有成员都是Send的,则该类型也是Send的);
  3. Sync允许多线程访问:Sync trait表明一个类型是可以安全的在多个线程中拥有其引用的;Send trait同时也是Sync的(因为Send是线程安全的);Rc、RefCell、Cell系列类型不是Sync的,Mutex<T>是Sync的;
  4. 手动实现的Send和Sync是不安全的:Send和Sync只是标记trait,它们甚至不需要实现任何方法,只是用来加强并发相关的不可变性的;

对象

对象、封装、继承,对象由数据和操作数据的方法组成;
Rust中实现对象:结构体、枚举类型加上impl块就组成的对象;
Rust中没有继承的概念,rust可以同trait进行行为的共享(类似于继承);

封装及工程编写

封装就是定义结构体并添加相应impl块及方法;
创建lib、编写toml文件等;创建工程的过程比较多,这里不做介绍,前面的文档中有过程,具体流程需要做大型项目的时候再说;

trait对象

实现一个库并定义struct(toml等的操作忽略);
dyn是rust关键字,表示是定义了某个trait的对象,及特征对象(类似于规定模板对象中规定实现模型trait<T: Draw>,但是和模板不一样的是,T只是一个固定的类型,而dyn又类似规定了一个父类类型,可以使用多种子类类型,如在容器中放入多个子类类型对象);(个人理解:dyn是为了弥补rust中没有继承的特点,如C++中可以在容器中规定父类指针,真正放入容器的可能是多种子类指针);
dyn为动态分发的意思:通过指针类型动态确定对象;(类似于C++中虚指针)

pub trait Draw {
	fn draw(&self);
}
// pub struct Screen<T: Draw> {
// 		pub component: Vec<T>;
// }
pub struct Screen {
	// dyn Draw表示Box中对象必须实现Draw trait
	pub component: Vec<Box<dyn Draw>>,
}
pub struct SelectBox {
	pub wight: i32,
	pub hight: i32,
	pub label: String,	
}
impl Draw for SelectBox {
	pub fn draw(&self) {
		...
	}
}

fn main() {
	// dyn定义的对象需要动态分发
	let s = Screen {
		component: vec![
			Box::new SelectBox {
				wight: 1,
				hight: 2,
				label: String::from("abc"),
			},
		],
	}
}
trait对象安全

使用trait对象,要求对象必须是安全的;只用对象安全的trait,才可以组成trait对象,trait安全的对象需要满足下面两个条件:

  1. 返回值类型不为self;
  2. 方法没有任何泛型类型参数;
  3. 不能有非self的方法(即入参必须有&self,类似C++ static方法)

模式

模式用法
  1. match
  2. if let模式
  3. while let模式匹配
  4. for模式匹配
  5. let模式
  6. 函数参数模式
//1、模式是Rust中特殊的语法,模式用来匹配值的结构。                                                                                                                          
//
//2、模式由如下内容组成:
//(1)字面值
//(2)解构的数组、枚举、结构体或者元组
//(3)变量
//(4)通配符
//(5)占位符

//1、match
//match VALUE {
//    PATTERN => EXPRESSION,
//    PATTERN => EXPRESSION,
//    PATTERN => EXPRESSION,
//}
//必须匹配完所有的情况
//fn main() {
//    let a = 1;    
//    match a {
//        0 => println!("Zero"),
//        1 => println!("One"),
//        _ => println!("other"),
//    };
//    println!("Hello, world!");
//}

//if let
//fn main() {
//    let color: Option<&str> = None;
//    let is_ok = true;
//    let age: Result<u8, _> = "33".parse();
//
//    if let Some(c) = color {
//        println!("color: {}", c);
//    } else if is_ok {
//        println!("is ok");
//    } else if let Ok(a) = age {
//        if a > 30 {
//            println!("oh, mature man");
//        } else {
//            println!("oh, young man");
//        }
//    } else {
//        println!("in else");
//    }
//}

//while let
//只要模式匹配就一直执行while循环
//fn main() {
//    let mut stack = Vec::new();
//    stack.push(1);
//    stack.push(2);
//    stack.push(3);
//
//    while let Some(top) = stack.pop() {
//        println!("top = {}", top);
//    }//只要匹配Some(value),就会一直循环
//}

//for 
在for循环中,模式是直接跟随for关键字的值,例如 for x in y,x就是对应的模式
//fn main() {
//    let v = vec!['a', 'b', 'c'];
//    for (index, value) in v.iter().enumerate() {
//        println!("index: {}, value: {}", index, value);
//    }
//}
//此处的模式是(index, value)

//let 
//let PATTERN = EXPRESSION
//fn main() {
//    let (x, y, z) = (1, 2, 3); //(1, 2, 3)会匹配(x, y, z),将1绑定到x,2绑定到y,3绑定到z
//    println!("{}, {}, {}", x, y, z);
//
//    let (x, .., z) = (1, 2, 3);
//    println!("{}, {}", x, z);
//}

//函数
//函数的参数也是模式
fn print_point(&(x, y): &(i32, i32)) {
    println!("x: {}, y: {}", x, y);
}

fn main() {
    let p = (3, 5);
    print_point(&p);
}
//&(3, 5) 匹配模式 &(x, y)

//模式在使用它的地方并不都是相同的,模式存在不可反驳的和可反驳的
模式的不可反驳和可反驳

模式有两种,可反驳(refutable)的和不可反驳(irrefutable),能匹配任何传递的可能值的模式称为不可反驳的(如match);对值的匹配可能失败的模式称为可反驳的(如if let);

  1. 函数参数、let语句、for循环只能接收不可反驳模式,因为如果匹配不成功程序无法进行有意义的行为;
  2. if let和while let只能接收可反驳的模式,因为它们的定义就是为了处理可能失败的条件;
所有模式语法介绍
  1. 匹配字面值
fn main() {
	let x = 1;
	match x {
		1 => println!("1"),
		2 => println!("2"),
		_ => println!("xx");
	}
}
  1. 匹配命名变量
fn main() {
    let x = Some(5); // x是一个option
    let y = 10; //位置1
    match x {
        Some(50) => println!("50"), // 值为50的some匹配这个
        Some(y) => println!("value = {}", y), //此处的y不是位置1的y,所有非50的some都会匹配这个
        _ => println!("other"),
    };

    println!("x = {:?}, y = {:?}", x, y); //此处的y是位置1的y
}
  1. 多个模式匹配,通过‘|’可以同时匹配上多个值
fn main() {                                                                                                                                                                  
    let x = 1;
    match x {
        1|2 => println!("1 or 2"), //|表示是或,匹配1或者2
        3 => println!("3"),
        _ => println!("xx"),
    };
}
  1. 使用’..'进行匹配,进行区间匹配
fn main() {
	// 对数字进行匹配
    let x = 5;                                                                                                                                                               
    match x {
        1..=5 => println!("1 to 5"), // 1|2|3|4|5 匹配1到5包括5;
        _ => println!("xx"),
    };

	// 对字符进行匹配
    let y = 'c';
    match y {
        'a'..='j' => println!("1"),
        'k'..='z' => println!("2"),
        _ => println!("other"),
    }
}
  1. 解构并分解,主要用于解构元组、结构体、枚举、引用
    解构结构体
struct Point {
    x: i32,
    y: i32,
}

fn main() {
	/ / 写法1let
    let p = Point{x: 1, y: 2};                                                                                                                                               
    let Point{x: a, y: b} = p; // 解构p,将x的值放在a里,将y值放在b里;
    assert_eq!(1, a); 
    assert_eq!(2, b); 
        
    // 写法2,let
    let Point{x, y} = p; // 类似于写法 let Point{x: x, y: y} = p;
    assert_eq!(1, x); 
    assert_eq!(2, y); 

	// 写法3,使用match匹配,对结构体部分值进行解构
    let p = Point{x: 1, y: 0}; 
    match p { 
    	Point{x: 1, y: 0} => println!("matched"),  // 同时解构x和y
        Point{x, y: 0} => println!("x axis"), // 解构y
        Point{x: 0, y} => println!("y axis"), // 解构x
        Point{x, y} => println!("other"),
    };  
}

解构枚举类型

enum Message {                                                                                                                                                               
    Quit,
    Move{x: i32, y: i32},
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);
    match msg {
        Message::Quit => {
            println!("quit");
        },
        Message::Move{x, y} => {
            println!("move, x: {}, y: {}", x, y); 
        },
        Message::Write(text) => println!("write msg: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!("clolor, r: {}, g: {}, b: {}", r, g, b); 
        },
    };  
}
----------------------------------------------------------------------------
// 嵌套枚举类型解构匹配
enum Color {                                                                                                                                                                 
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move{x: i32, y: i32},
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("rgb color, r: {}, g: {}, b: {}", r, g, b); 
        }, // 匹配ChangeColor中的Rgb
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("hsv color, h: {}, s: {}, v: {}", h, s, v); 
        },
        _ => ()
    };  
}

解构结构体和元组的混合

struct Point{                                                                                                                                                                
    x: i32, 
    y: i32,
}

fn main() {
    let ((a, b), Point{x, y}) = ((1, 2), Point{x: 3, y: 4}); // 元组中包含子元组和结构体
    println!("a: {}, b: {}, x: {}, y: {}", a, b, x, y); 
}
  1. 匹配时忽略模式中的值:rust中使用_来匹配任意值
fn foo(_: i32, y: i32) {
    println!("y = {}", y); 
}

//trait A {                                                                                                                                                                  
//    fn bar(x: i32, y: i32);
//}
//
//struct B {}
//
//impl A for B {
//    fn bar(_: i32, y: i32) { // 忽略函数中第一个入参
//        println!("y = {}", y);
//    }
//}

fn main() {
    foo(1, 2); 

    let numbers = (1, 2, 3, 4); 
    match numbers {
        (one, _, three, _) => { // 使用_忽略元组中部分值
            println!("one: {}, three: {}", one, three);
        },  
    }   
}

// 变量名加_前缀,表示编译器忽略该变量;
fn main() {
    let _x = 5; // 忽略此变量;
    let _y = 5; // 忽略此变量;

    //let s = Some(String::from("hello"));
    //if let Some(_c) = s {
    if let Some(c) = s {
    //    println!("found a string");
    //}
    println!("s: {:?}", s);
    
    let s = Some(String::from("hello"));
    if let Some(_) = s {
        println!("found a string");
    }
    println!("s: {:?}", s);
}
  1. 匹配守卫:match分支提供额外的if条件
fn main() {
    let num = Some(4);
    let y = 10; //位置1
    match num {
        Some(x) if x == y => println!("num == y"),//此处的y是位置1处的y
        Some(x) => println!("x: {}", x), 
        None => (), 
    };  

    let x = 4;
    let y = false;
    match x { 
        4|5|6 if y => println!("1"),//4|5|6 if y  a: 4|5|(6 if y)   b: ((4|5|6) if y)(等价于此种)
        _ => println!("2"),
    }   
}

  1. @运算符:允许我们在创建一个存放值的变量的同时,测试这个变量的值是否匹配模式
enum Message {
    Hello{id: i32},
}

fn main() {
    let msg = Message::Hello{id: 25};
    match msg {
        Message::Hello{id: id_va @ 3..=7} => { // 创建变量id_va,并匹配3到7
            println!("id_va: {}", id_va);
        },
        Message::Hello{id: 10..=20} => { // 匹配10到20
            println!("large");
        },
        Message::Hello{id} => { // 同名变量可以忽略
            println!("id: {}", id);
        },
    }   
    println!("Hello, world!");
}

不安全的rust

不安全rust介绍:unsafe关键字

1、在此节之前讨论过的都是安全的Rust,即Rust在编译时会强制执行的内存安全保证。不会强制执行这类内存安全保证的,就是不安全的Rust。

2、不安全的Rust存在的两大原因:
(1)静态分析本质上是保守的,就意味着某些代码可能是合法的,但是Rust也会拒绝。在此情况下,可以使用不安全的代码。
(2)底层计算机硬件固有的不安全性。如果Rust不允许进行不安全的操作,有些任务根本就完成不了。

3、不安全的Rust具有的超级力量
Rust会通过unsafe关键字切换到不安全的Rust。不安全的Rust具有以下超级力量:
(1)解引用裸指针
(2)调用不安全的函数或者方法
(3)访问或修改可变静态变量
(4)实现不安全的trait
注意:unsafe并不会关闭借用检查器或禁用任何其它的Rust安全检查规则,它只提供上述几个不被编译器检查内存安全的功能。unsafe也不意味着块中的代码一定就是不ok的,它只是表示由程序员

// 解引用裸指针                                                                                                                                                            
// 不可变和可变的,分别写作*const T, *mut T
//
// (1)允许忽略借用规则,可以同时拥有不可变和可变的指针,或者是多个指向相同位置的可变指针
// (2)不保证指向的内存是有效的
// (3)允许为空
// (4)不能实现任何自动清理的功能
fn main() {
    let mut num = 5;
    //创建不可变和可变的裸指针可以在安全的代码中,只是不能在不安全代码块之外解引用裸指针
    let r1 = &num as *const i32; // 使用as进行类型转换,将引用转换为裸指针
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1 is: {}", *r1); // 使用裸指针才需要unsafe包起来
        println!("r2 is: {}", *r2);
    }   

    let add = 0x12345usize;
    let _r = add as *const i32;
}
unsafe函数
// 调用不安全的函数或者方法                                                                                                                                                
unsafe fn dangerous() { // unsafe函数
    println!("do something dangerous");
}

fn foo() {
    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("*r1 = {}", *r1);
        println!("*r2 = {}", *r2);
    }   
}

fn main() {
    unsafe {
        dangerous();
    }   
    //dangerous(); //error

    foo();
    println!("Hello, world!");
}
Rust FFI

调用c语言的函数

extern "C" { // 声明c函数接口
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-3): {}", abs(-3));
    }   
    println!("Hello, world!");
}

c语音调用rust函数

  1. 编写rust lib,并编译为静态库
    rust lib toml配置文件
[package]                                                                                                                                                                    
name = "foo"
version = "0.1.0"
authors = ["anonymousGiga <cryptonymGong@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[lib]
name = "foo"
crate-type = ["staticlib"]

rust lib编写

#![crate_type = "staticlib"]                                                                                                                                                 
#[no_mangle]
// 定义foo函数,以供c程序使用
// 不懂这个函数为什么会使用extern
pub extern fn foo() { 
    println!("use rust");
}
  1. c文件main.c中声明rust函数;
extern void foo(); // 声明rust接口                                                                                 
int main() {
  foo();
  return 0;
}
  1. 编译时将rust编译好后的foolib.a库文件链接到c程序中;注意:因为rust的依赖问题,编译需要加上链接依赖-lpthread和-ldl
    gcc -o main main.c libfoo.a -lpthread -ldl
访问或修改可变静态变量

静态变量和常量的区别:
1、静态变量有一个固定的内存地址(使用这个值总会访问相同的地址),常量则允许在任何被用到的时候复制其数据。
2、静态变量可以是可变的,虽然这可能是不安全(用unsafe包含)

//static HELLO_WORLD: &str = "Hello, world";
//
//fn main() {
//    println!("{}", HELLO_WORLD);
//}

static mut COUNTER: u32 = 0;
fn add_counter(inc: u32) {
    unsafe {
        COUNTER += inc;
    }   
}

fn main() {
    add_counter(3);
    add_counter(3);
    unsafe {
        println!("counter: {}", COUNTER);
    }   
}
实现不安全的trait
  1. 当至少有一个方法中包含编译器不能验证的不变量时,该trait就是不安全的。
  2. 在trait之前增加unsafe声明其为不安全的,同时trait的实现也必须用unsafe标记。
unsafe trait Foo {
    fn foo(&self);
}

struct Bar();

unsafe impl Foo for Bar {
    fn foo(&self) {
        println!("foo");
    }   
}

fn main() {
    let a = Bar();
    a.foo();
    println!("Hello, world!");
}

类型

关联类型与模板
  1. 关联类型在trait定义中指定占位符类型
    关联类型是一个将类型占位符与trait相关联的方式。
    trait 的实现者会针对特定的实现在这个类型的位置指定相应的具体类型。
    如此可以定义一个使用多种类型的 trait。
  2. 关联类型与模板区别:
    1) 定义关联类型的trait是一种类型,定义模板的trait根据泛型的不同是不同类型
    2) 调用trait中方法的形式不一样,关联类型定义的trait可以直接调用方法,而泛型需要使用完全限定语法调用
// 定义关联类型
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

// 定义与关联类型类似的模板
pub trait Iterator1<T> {
    fn next(&mut self) -> Option<T>;
}

struct A {
    value: i32,
}

impl Iterator1<i32> for A {
    fn next(&mut self) -> Option<i32> {
        println!("in i32");
        if self.value > 3 {
            self.value += 1;
            Some(self.value)
        } else {
            None
        }
    }
}
impl Iterator1<String> for A {
    fn next(&mut self) -> Option<String> {
        println!("in string");
        if self.value > 3 {
            self.value += 1;
            Some(String::from("hello"))
        } else {
            None
        }
    }
}

fn main() {
    let mut a = A{value: 3};
    //a.next(); // 如果A实现的是关联类型的trait,则可以直接调用
    // 使用泛型实现的的struct调用trait中函数时必须标注类型,使用完全限定语法
    <A as Iterator1<i32>>::next(&mut a);  //完全限定语法,带上了具体的类型
    <A as Iterator1<String>>::next(&mut a);
    println!("Hello, world!");
}
默认泛型参数与运算符重载
  1. 使用泛型类型参数时,可以为泛型指定一个默认的具体类型。
  2. 运算符重载是指在特定情况下自定义运算符行为的操作;Rust并不允许创建自定义运算符或者重载运算符, 不过对于std::ops中列出的运算符和相应的trait,我们可以实现运算符相关trait来重载。
use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

// 实现ops::Add trait,以重载+运算符
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.y,
            y: self.y + other.y,
        }   
    }   
}

#[derive(Debug)]
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters{
    type Output = Millimeters;
    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + other.0 * 1000)
    }   
}

fn main() {
    assert_eq!(Point{x: 1, y: 1} + Point{x:2, y: 2}, Point{x: 3, y: 3});
    let mi = Millimeters(1);
    let m = Meters(1);
    let r = mi + m;
    println!("r = {:?}", r);
    println!("Hello, world!");
}

//尖括号里面为泛型的默认类型参数,RHS是一个泛型类型参数(right hand side)
//trait Add<RHS=Self> {  // 这里泛型RHS默认是Self类型
//    type Output;
//    fn add(self, rhs: RHS) -> Self::Output;
//} 
完全限定语法

struct实现了一个或多个trait,但是具有多个同名的方法,分为两种情况来区分调用这些方法;

  1. trait中有同名方法调用,并且该方法中有self参数
trait A {                                                                                                                                                                    
    fn print(&self);
}

trait B { 
    fn print(&self);
}

struct MyType;

impl A for MyType {
    fn print(&self) {
        println!("A trait for MyType");
    }   
}

impl B for MyType {
    fn print(&self) {
        println!("B trait for MyType");
    }   
}

impl MyType {
    fn print(&self) {
        println!("MyType");
    }   
}

fn main() {
    let my_type = MyType;
    my_type.print(); //等价于MyType::print(&my_type);
    A::print(&my_type); // 将my_type作为self参数传入
    B::print(&my_type);
    println!("Hello, world!");
}
  1. trait中有同名方法,但是该方法没有self参数:使用完全限定语法
    完全限定语法定义:
    <Type as Trait>::function(…)
trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("baby_name: {}", Dog::baby_name());
    //println!("baby_name: {}", Animal::baby_name()); // 编译报错
    println!("baby_name: {}", <Dog as Animal>::baby_name()); //完全限定语法
}
父trait

父 trait 用于在另一个 trait 中使用某 trait 的功能,有时我们可能会需要某个 trait 使用另一个 trait 的功能,在这种情况下,需要能够依赖相关的 trait 也被实现。这个所需的 trait 是我们实现的 trait 的 父(超) trait(supertrait)。

use std::fmt;
trait OutPrint: fmt::Display { // 要求实现Display trait
    fn out_print(&self) {
        let output = self.to_string();
        println!("output: {}", output);
    }   
}

struct Point{
    x: i32,
    y: i32,
}

impl OutPrint for Point {}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }   
}

fn main() {
    println!("Hello, world!");
}
newtype模式

newtype 模式用以在外部类型上实现外部 trait;
孤儿规则(orphan rule):只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。
一个绕开这个限制的方法是使用 newtype 模式(newtype pattern)。

// 功能:为Vec实现Display trait,因为它们都不是在此crate中定义的,所以不能直接实现;因此我们定义一个Wrapper来包含Vec,再为Wrapper实现Display trait,从而间接的达到为Vec实现trait的功能;
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({})", self.0.join(","))
    }   
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("World")]);
    println!("w = {}", w); 
    println!("Hello, world!");
}
类型别名

使用type关键字为类型区别名,类似C++中using T = Type

type Kilometers = i32;

fn main() {
    let x: i32 = 5;        
    let y: Kilometers = 6;
    let r: i32 = x + y;
    println!("x + y = {}", r); 
}
never type

Rust 有一个叫做 !的特殊类型。在类型理论术语中,它被称为 empty type,因为它没有值。
我们更倾向于称之为 never type。在函数不返回的时候充当返回值

fn bar() -> ! {
    loop{}
}

以下例子为《Rust程序设计语言》中第二章“猜猜看”游戏的例子,不能直接允许,需要在toml里面添加依赖;

use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
    println!("Guess the number!");
    let secret_number = rand::thread_rng().gen_range(1, 101);
    loop {
        println!("Please input your guess.");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");
        let guess: u32 = match guess.trim().parse() { // 这里进行了变量隐藏,后定义的guess隐藏了前面的同名变量
            Ok(num) => num,
            Err(_) => continue, //continue 的值是 !。
                                //当 Rust 要计算 guess 的类型时,它查看这两个分支。
                                //前者是 u32 值,而后者是 ! 值。
                                //因为 ! 并没有一个值,Rust 根据前面的match返回值判断决定 guess 的类型是 u32
        };  
        println!("You guessed: {}", guess);
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }   
        }   
    }   
}

高级函数和闭包

函数指针允许我们使用函数作为另一个函数的参数。
函数的类型是 fn ,fn 被称为 函数指针。指定参数为函数指针的语法类似于闭包。
函数指针实现了Fn、FnMut、FnOnce

fn add_one(x: i32) -> i32 {
    x + 1 
}

fn do_twice(f: fn(i32) -> i32, val: i32) -> i32 {
    f(val) + f(val)
}

fn wapper_func<T>(t: T, v: i32) -> i32 
    where T: Fn(i32) -> i32 {
    t(v)
}

fn func(v: i32) -> i32 {
    v + 1 
}

fn main() {
    let r = do_twice(add_one, 5); 
    println!("r = {}", r); 

    //+++++++++++++++++
    let a = wapper_func(|x| x+1, 1); 
    println!("a = {}", a); 

    let b = wapper_func(func, 1); 
    println!("b = {}", b); 
}

将闭包作为返回值: Fn没有实现size,需要用Box包起来

//fn return_clo() -> Fn(i32) -> i32 {
//    |x| x+1
//}

fn return_clo() -> Box<dyn Fn(i32)->i32> {
    Box::new(|x| x+1)
}

fn main() {
    let c = return_clo();
    println!("1 + 1 = {}", c(1));
    println!("1 + 1 = {}", (*c)(1)); // 解引用多态
    println!("Hello, world!");
}

宏的介绍
  1. Rust中的宏主要有两种,一种是使用macro_rules!的声明宏,一种是过程宏。而过程宏又主要分为三种:
    (1)自定义宏#[derive],在结构体、枚举等上指定通过derive属性添加代码;
    (2)类属性宏,定义可用于任意项的自定义属性;
    (3)类函数宏,看起来像函数但是作用于作为参数传递的Token。

  2. 宏和函数
    (1)宏是一种为写其它代码而写代码的方式。宏对于减少大量编写代码和维护代码非常有用。
    (2)一个函数标签必须声明函数参数个数和类型,宏只接受可变参数。
    (3)宏的定义比函数的定义更复杂。
    (4)在调用宏 之前 必须定义并将其引入作用域,而函数则可以在任何地方定义和调用。

  3. 过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。

  4. 类属性宏
    类属性宏与自定义派生宏相似,不同于为 derive 属性生成代码,它们允许你创建新的属性。

例子:
可以创建一个名为 route 的属性用于注解 web 应用程序框架(web application framework)的函数:
#[route(GET, “/”)]
fn index() {

#[route] 属性将由框架本身定义为一个过程宏。其宏定义的函数签名看起来像这样:
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

说明:类属性宏其它工作方式和自定义derive宏工作方式一致。

  1. 类函数宏
    类函数宏定义看起来像函数调用的宏。类似于 macro_rules!,它们比函数更灵活。
    例子:
    如sql!宏,使用方式为:
    let sql = sql!(SELECT * FROM posts WHERE id=1);
    则其定义为:
    #[proc_macro]
    pub fn sql(input: TokenStream) -> TokenStream {

  2. 宏的资料推荐
    https://danielkeep.github.io/tlborm/book/mbe-macro-rules.html

定义过程宏的函数接受一个 TokenStream 作为输入并产生一个 TokenStream 作为输出。这也就是宏的核心:宏所处理的源代码组成了输入 TokenStream,同时宏生成的代码是输出 TokenStream。

use proc_macro;
#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
声明宏

vec!就是一个声明宏,这是一个模式匹配的过程,形式类似match其中*表示任意数量,()内是表达式

// 声明一个声明宏
#[macro_export]  // 表明这个宏可以被导出
macro_rules! my_vec { //my_vec! 模仿vec!用于创建vector
    ($($x: expr), *) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };  
}
自定义derive宏
// 过程宏中的derive宏
#[derive(Debug)] // 自动为结构体实现了fmt::Display trait
struct A { 
  a : i32,
}

实现一个自定义宏,能够为结构体自动实现HelloMacro trait

  1. 定义trait
pub trait HelloMacro {                                                                                                                                                       
    fn hello_macro();
}
  1. 使用proc_macro_derive定义HelloMacro宏的实现,其实是实现一个输入输出都是TokenStream的fn
extern crate proc_macro;                                                                                                                                                     
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, in my macro, my name is {}", stringify!(#name));
            }
        }
    };  

    gen.into()
}

// 输入和
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap(); // 解析输入为DeriveInput
    impl_hello_macro(&ast)
}

DeriveInput结构体是quote包中定义的,大致如下:

DeriveInput {
    // --snip--

    ident: Ident {
        ident: "Pancakes",
        span: #0 bytes(95..103)
    },  
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Unit,
            semi_token: Some(
                Semi
            )   
        }   
    )   
}
  1. 使用HelloMacro宏,为结构体自动实现HelloMacro trait
use hello_macro::HelloMacro;                                                                                                                                                 
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Main;

fn main() {
    Main::hello_macro(); // 调用HelloMacro的hello_macro方法
    println!("Hello, world!");
}

Rust组件使用

rust使用json

Cargo.toml文件中添加依赖

[dependencies]
serde = "*"
serde_derive = "*"
serde_json = "*"

json库采用serde json, 文档:https://serde.rs/

从文件解析出json数据
extern crate serde_json;
use std::fs::File;

fn preparedata() -> String {
    let f = File::open("/export/rust/test_json/sample.json").unwrap();                
    let values : serde_json::Value = serde_json::from_reader(f).unwrap();
    serde_json::to_string(&values).unwrap()
}
fn main() {
    let r = preparedata();
    println!("r:{}", r); 
}
往serde_json对象中添加object
extern crate serde_json;
use serde_json::{Value, json};

fn test_json() -> String {
    let mut value = json!({});
    value.as_object_mut().unwrap().insert("f1".to_string(), Value::String("v1".to_string()));
    value.as_object_mut().unwrap().insert("f2".to_string(), Value::String("v2".to_string()));
    serde_json::to_string(&value).unwrap()
}

fn main() {
    let r = test_json();
    println!("r:{}", r);
}

rust 使用protobuf

编译proto文件为rs文件,并使用
  1. 安装protoc: yum install -y protobuf
  2. 安装protoc-gen-rust插件:cargo install protobuf,使用protoc编译proto文件为rs文件需要此插件
  3. 安装protobuf-codegen-pure:cargo install protobuf-codegen-pure,使用rust脚本编译proto文件为rs文件需要(也可以后面将protobuf-codegen-pure的依赖写入,cargo会自动下载);
  4. 编写proto文件:test.proto
syntax="proto2";                                                                                                                                                                                                                    

message testpb {
  optional int32 left   = 1;
  optional string right = 2;
}
  1. 编译proto文件为test.rs文件,有两种方式
    1) 使用protoc命令编译:‘protoc --rust_out . test.proto’
    2) 使用rust脚本编译
    编写build.rs
    fn main() {
    	protobuf_codegen_pure::Codegen::new()
       		.out_dir("src/protos")
        	.inputs(&["protos/test.proto"])                                               
        	.include("protos")
        	.run()
        	.expect("Codegen failed.");
    }
    
    Cargo.toml文件中导入依赖,build.rs脚本需要此依赖包,这里选3.0.0-alpha.1是因为json转pb是在3.0版本后才有的功能,而编译proto文件使用的protobuf-codegen-pure和后面使用的protobuf需要版本一致,否则编译出错:
    [build-dependencies]
    protobuf-codegen-pure = "3.0.0-alpha.1"
    
    编译test.proto为test.rs,build.rs中设置的输出文件夹是src/protos,这个需要提前创建,编译命令:cargo build,并为protos目录创建mod.rs,src目录创建lib.rs
    protos/mod.rs 内容为:
    pub mod test;
    
    src/lib.rs内容为:
    pub mod protos;
    
    最终的目录结构:
    .
    ├── build.rs
    ├── Cargo.toml
    ├── protos
    │   └── test.proto
    └── src
    	├── main.rs
    	├── lib.rs
    	└── protos
    		├── mod.rs
        	└── test.rs
    
  2. 在代码中使用test.proto定义的数据,在Cargo.toml中导入protobuf依赖(我安装的protoc版本是3.5,这里的依赖是protobuf-rust库的版本,不是protoc编译器版本,protobuf版本和前面的protobuf-codegen-pure版本一致),并编写main.rs
    Cargo.toml文件导入依赖:
[dependencies]
protobuf = "3.0.0-alpha.1"  

main.rs中使用proto中定义的数据结构testpb:

use test_pb::protos::test::*;

fn main() {
    let mut p = Testpb::new();                                                
    p.set_left(3);
    p.set_right(String::from("hello"));

    println!("p:{ :?}", p); 
}
json数据转pb

protobuf依赖需要3.0以上;
以上面的test.proto为例,编译出来Testpb的proto对象;

extern crate protobuf;
use serde_json::{Value, json};
use test_pb::protos::test::*;

fn test_pb() {
    let mut p = Testpb::new();
    p.set_left(3);
    p.set_right(String::from("hello"));

    println!("p:{:?}", p);
}

fn test_json_pb() {
	// 构造json字符串
    let mut value = json!({});
    value.as_object_mut().unwrap().insert("left".to_string(), serde_json::from_str("3").unwrap());
    value.as_object_mut().unwrap().insert("right".to_string(), Value::String("hello".to_string()));
    let s = serde_json::to_string(&value).unwrap(); // json字符串
    
    // json字符串转Testpb
    // parse_from_str为泛型函数,本在protobuf::json::parse模块下,parse是私有模块,protobuf中使用了pub use来公开parse_from_str函数;
    let p = protobuf::json::parse_from_str::<Testpb>(&s).unwrap();
    println!("p:{:?}", p);
    let left = p.get_left();
    let right = p.get_right();
    println!("{},{}", left, right);
}
fn main() {
    test_json_pb();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值