Rust语言圣经(Rust Course)
命名规范 - Rust语言圣经(Rust Course)
Rust安装配置_rust下载到d盘_Andreby的博客-CSDN博客
Clion中,安装Rust插件,进行如下配置:
...\.rustup\toolchains\stable-x86_64-pc-windows-gnu\bin
...\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib\rustlib\src\rust
这里不知道什么时候可以自动填入了,且toolchain路径为
...\.cargo\bin
。可能是因为我下了且在用nightly版本。
如果Standard Library找不到的话,运行下方命令即可:
rustup component add rust-src --toolchain stable-x86_64-pc-windows-gnu
rustup doc
可以离线查看教程。
换源,...\.cargo\config
:
[source.crates-io]
# 替换成你偏好的镜像源
replace-with = 'tuna'
# 清华大学 5mb
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
# 中国科学技术大学 2mb
[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index"
# 上海交通大学 2mb
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"
# rustcc社区 2mb
[source.rustcc]
registry = "https://crates.rustcc.cn/crates.io-index"
# 字节跳动 10mb
[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"
Rust是静态强类型的。
代码包或库被称为crate。
变量默认为immutable,即不可变变量
,不可进行二次赋值;想要让它可变需要在声明时加上mut
。
use很像cpp的using namespace。有的crate是默认导入(prelude,预导入模块)的,如std::io。
use std::io;
fn main() {
println!("Input: ");
let mut input=String::new(); //声明并用String的new赋值一个可变变量
io::stdin().read_line(&mut input).expect("Fail to read line");
//read_line返回 io::Result,交由expect处理
println!("Your input is: {}",input)
}
trait: 可视为接口,提供了一些方法,如rand::Rng
。
不应当使用while true{...}
来表示无限循环,而是使用loop{...}
,因为rust在哲学上追求高度一致,while仅用于while condition。
数字可以使用下划线分割来增加可读性,如100_000。
变量名、函数名使用snack_case命名规范,其他都是大驼峰。
有些东西在预导入模块(prelude)里,不需要use。
T代表所有类型,包括&T和&mut T;而&T和&mut T完全没有交集。
rust允许定义同名的新变量来shadow(隐藏)旧变量,常用于类型转换。新旧的变量的类型可以完全不同,也可以都是不可变变量。
let valName: type = value
可以显式定义变量类型。
类型推断可以做到从下往上推断。而所有变量应当在编译期就能推断出确定的类型。
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
isize和usize由计算机架构决定,64位计算机就是64bit。主要用于对某种集合进行索引操作。
整数默认类型是i32。
字面值:
Number Literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0077 |
Binary | 0b1111_0000 |
Byte(u8 only) | b’A’ |
除Byte以外都能加类型后缀,如57u8。
在调试模式下发生溢出则会panic;发布模式下256u8会变成0,257u8变成11。
IEEE-754
bool
占1个字节。
char
占4个字节。使用unicorn编码。
Rust提供了两个基本复合类型:元组Tuple和数组。
使用点-索引进行取值。
let tp: (i32, i64, f64) = (45, 32, 6.7);
println!("tp: {},{},{}", tp.0, tp.1, tp.2);
可以使用解构赋值来拆分:
let tp: (i32, i64, f64) = (45, 32, 6.7);
let (t0, t1, t2) = tp;
println!("tp: {},{},{}", t0, t1, t2);
也可以以此让函数返回多个值:
fn calculate_values() -> (i32, i32) {
let x = 5;
let y = 10;
(x, y)
}
长度是固定的。
用数组而不用vector通常是为了保存固定长度的数据或者让数据存在栈而非堆上。
let array = [12,34,63];
标注类型时可以标注长度,如:
let arr: [i32:5] = [1,2,3,4,5]
let arr = [3;5]
等价于let arr = [3,3,3,3,3]
。
raft禁止数组越界访问,会在运行时报错。编译时只能检查出简单的越界错误。
表达式加了分号就算是语句了。
语句的返回值为空的Tuple:()
,而表达式返回其值。块的最后一个表达式或语句会成为块的返回值:
let y = {
let x=2;
x+1
};
let z = if 99>88{ 1 } else { -1 }; //为了满足一致性,以此替代三目表达式
函数不需要关心前后关系,不需要前向声明。
fn fun_name(x: i32,y: i32) -> i32 {
println!("fun {} {}",x,y);
x+y
}
(1..4)
就是个Range类型,表示 [ 1 , 4 ) [1,4) [1,4) ,即1,2,3。(1..4).rev()
则翻转变成3,2,1。而(1..=4)
则为 [ 1 , 4 ] [1,4] [1,4] ,包含4。
可以被for循环遍历,也可使用迭代器方法。
//阶乘
pub fn factorial(num: u64) -> u64 {
(1..=num).fold(1, |acc, e| acc * e)
}
let arr = [11, 22, 33, 44, 55];
//使用迭代器
for ele in arr.iter() {
println!("for1: {}", ele);
}
//使用Range
for index in 0..5 {
println!("for2: {}", arr[index]);
}
//enumerate对迭代器进行包装,返回Tuple
for (i,&ele) in arr.iter().enumerate(){
println!("for3: {} {}", i, ele);
}
for不仅可以结构,还能通过&item
接收来进行解引用,可以理解为&item=&i32
即为item=i32
。
fn func1(list: &[i32]){
let mut largest: i32 = 0;
for &item in list{
if item > largest {
largest = item;
}
}
largest
}
//等价于
fn func2(list: &[i32]){
let mut largest: i32 = 0;
for item in list{
if *item > largest {
*largest = item;
}
}
largest
}
//定义
struct StructName{
variable_name: type,
...
}
//实例化
//缺少字段会报错
let object_name = StructName{
variable_name: value,
...
};
整个struct要么都可变,要么都不可变。
像js一样,字段值和给它赋值的变量名一样时可简写,如v_name: v_name
可直接写成v_name
。
struct的所有引用变量都要进行生命周期标注。标注后,该变量的生命周期必须要长于struct。
如果S的两个实例s1和s2只有若干字段不同,则可以先给这几个字段赋值,然后加上..s2
:
let person = Person {
name: String::from("Alice"),
age: 20,
};
let updated_person = Person {
name: String::from("Bob"),
..person
};
类似于对Tuple的typedef。
struct Color(i32,i32,i32);
let black = Color(0,0,0);
没有定义任何字段是struct,与()
(空Tuple)(单元类型)相似。想实现trait但不需要存储数据的时候会用到。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
方法的第一个参数总是self,非引用、不可变引用或可变引用都行。
调用方法时,会自动给调用方法的对象加上&
、&mut
或*
,以匹配方法中的self。
一个结构体的可以写多个impl块。
如果定义的时候没有传入self,则称为关联方法。对象无法调用关联方法,只能使用结构体名::关联函数名
的形式调用,类似于其他语言的静态函数。
struct Circle {
radius: f64,
}
impl Circle {
fn new(radius: f64) -> Circle {
Circle { radius }
}
}
给struct加上pub后,可以被上级模块访问到,但其内部的成员默认都是私有的。想让成员公有则必须在成员的前面也加上pub。
以结构体的方式解构变量。
let mut a = A {
x: 1,
y: 2,
};
let A { x, y } = &mut a; //x和y都是&mut引用且生命周期相同
枚举的值种类叫做变体。枚举的访问使用MyEnum::V1
。
enum Direction {
North,
South,
East,
West,
}
let direction = Direction::North;
变体可以携带数据,且各个变体能有不同的类型:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
let msg = Message::ChangeColor(0, 160, 255);
枚举也能和方法一样用impl定义方法。
给enum加上pub后,可以被上级模块访问到,且其内部的成员默认都为公共。
是为了去除null而诞生的,表示该类型变量可空。不用Option
的都可以直接视为不可能空,可放心使用。
在预导入模块中,Option
、Some(T)
、None
都是能直接用的。
//标准库中的定义
enum Option<T>{
Some(T),
None, //表示变量为空
}
Option
和T
被视为两种类型,不能混用。若想把Option
当成T
用,则需要先进行类型转换(相当于强制检测一次null)。
let num1: Option<i32> = None;
let num2: Option<i32> = Some(5);
使用match对空进行判断:
match x {
None => println!("Empty"),
Some(i) => println!("{}",i),
}
使用unwrap_or_default()
,在Some时返回提取的内容,在None时返回Default trait的default()
的结果。
and(x)
: 如果是Some,则返回Some(x)
,否则直接返回None。
and_then(|x| {...})
: 如果是Some,则将内部数据当做闭包参数调用闭包,且闭包返回值类型是Option;否则直接返回None。
Rust没有异常系统。
Result
panic!
宏panic发生时,默认会展开(unwind) 调用栈,往回走,把遇到的所有函数的数据都给清理掉。可以选择不展开,而是中止(abort),不清理而直接停止程序,由操作系统回收内存。中止也可以让二进制文件更小。
[profile.release]
panic = 'abort'
为了让panic发生时打印回溯错误信息,需要:
#windows
set RUST_BACKTRACE=1 && cargo run
#linux
RUST_BACKTRACE=1 cargo run
enum Result<T,E> {
Ok(T),
Err(E),
}
可以把T或E写成()
表示不关心该返回值。
可以提取出Err的参数热然后调用kind()以进一步match错误类型:
use std::fs::File;
use std::io::ErrorKind;
let file = File::open("non_existent_file.txt");
match file {
Ok(_) => println!("File opened successfully"),
Err(e) => match e.kind() {
ErrorKind::NotFound => println!("File not found"),
ErrorKind::PermissionDenied => println!("Permission denied"),
other => println!("Some other error: {:?}", other),
},
}
也可以直接调用unwrap方法,如果是Ok则返回Ok的数据,否则调用panic!。但无法自定义panic信息。
let file = File::open("non_existent_file.txt").unwrap();
若调用expect方法即可自定义panic信息。
let file = File::open("non_existent_file.txt").expect("Expect Panic Msg");
只要返回值为result,就可以传递错误,如可以写为Result
。
使用?
运算符可以在输出为Result::Err的时候自动将其return。
但是使用?
时要注意,它在成功的时候返回的并不是Result,因此正确地返回时应当用Ok再做包装。
在?
返回的Err与要求的返回值不匹配时,会隐式地调用from函数进行转换,前提是转化的目标错误对原错误实现了from函数。
use std::fs::File;
use std::io;
use std::io::Read;
fn read_file() -> io::Result<String> { //等价于Result
let mut f = File::open("file.txt")?;
let mut buffer = String::new();
f.read_to_string(&mut buffer)?; //等价于match到Err(e)后return Err(e)
Ok(buffer)
}
fn main() {
match read_file() {
Ok(content) => println!("File content: {}", content),
Err(e) => println!("An error occurred: {}", e),
}
}
使用ok()
或err()
方法可以将Result枚举转换为Option枚举:
let x: Result<u32, &str> = Ok(2);
assert_eq!(x.ok(), Some(2));
let x: Result<u32, &str> = Err("Nothing here");
assert_eq!(x.ok(), None);
如果确定必然是Ok的,则直接使用unwrap获取结果值。
let p = Point{ x: 1 , y: 3 };
let Point{ x_num , y_num } = p;
let Point{ x , y } = p; //解构的变量名和结构体的成员变量名相同的时候可简写
static MY_STATIC_VA: &str = "Hello World";
static mut COUNT: u32 = 0;
'static
即使是可变静态变量,对其的修改也是不安全的,要在unsafe中进行。Unsafe Rust
main函数的默认返回值为()
,即单元类型。但也可以改成Result
,表示可以接收任意错误,但要在main末尾添上Ok(())
。
use std::fs::File;
use std::io::Read;
fn read_file() -> Result<String, Box<dyn std::error::Error>> {
let mut f = File::open("file.txt")?;
let mut buffer = String::new();
f.read_to_string(&mut buffer)?;
Ok(buffer)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = read_file()?;
println!("File content: {}", content);
Ok(())
}
对于某个值(在堆中),当拥有它的变量走出作用范围时,立刻会自动调用drop函数将此堆内存释放。
当一个值以Move的形式被赋给另一个值的时候,先前的值会失效。这样可以防止在调用drop的时候出现二次释放。标量类型及其组合(如标量组合的Tuple)不会触发Move。
let s1=String::from("abc");
let s2=s1; //此时s1完全不可用
想触发拷贝则需要使用clone函数:
let s1=String::from("abc");
let s2=s1.clone(); //s1依旧可用,且堆内存也被拷贝了(深拷贝)
对于完全存放在栈上的数据类型,它们不需要Move,因此如果一个数据类型实现了Copy这个trait,则赋值的时候会进行克隆而非Move。但是,如果实现了Drop trait,就禁止实现Copy trait,以防止二次drop。TODO:似乎工程意义上要先实现Clone trait?
函数的传参与返回都会触发所有权的转移。因此若String被作为普通的参数传入函数的话,会在函数结束时失效。
let ref = &v;
引用此时有了新的解释:能获取值,但不转移所有权。把引用作为函数参数的行为叫做借用。有借有还。引用想可变也需要mut。
&mut v
作为参数,即可变借用也会判为可变引用。引用的作用域是声明->最后一次使用。因此,所有不可变引用不再使用后就马上可以进行修改。
悬空引用(Dangling Reference)(可理解为野指针)永远不会发生,因为编译时,若引用未离开作用域(如被函数返回)但数据离开作用域的话就会报错。
to_owned()
对一个实现了Clone trait的引用(如&str
)进行to_owned()
,就会通过拷贝得到一个带有所有权的变量(如String
)。
深入生命周期 - Rust语言圣经(Rust Course)
生命周期的目的是为了避免悬空引用。
编译器在编译时会列出各个变量生命周期的长度,保证不发生数据失效后的使用。但是有些情况无法在编译时得知生命周期:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
//即使这样也会报错
fn longest(x: &str, y: &str) -> &str {
x
}
编译器不看函数内部,而只看函数签名来推断返回值的生命周期。
<'a>
为泛型生命周期参数。而生命周期标注(&'a str
)仅仅用于描述关系,而不直接影响生命周期长度。
下面的代码给所有参数和返回值都进行了同一个标注,表示在该函数范围内它们的生命周期完全一样。从因果逻辑上来看,'a
首先获得s1和s2的生命周期,并取交集,即取比较短的部分,然后规定返回值的生命周期必须在这部分里有效。
// 这是一个函数,它接受两个字符串切片,并返回一个字符串切片。
// 'a 是一个生命周期参数,它表示输入参数和返回值的生命周期必须“相同”。
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
//正常的代码
fn main() {
let string1 = String::from("long string is long");
let result;
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
//会报错,因为result的生命周期取为string2,不能超出下一个花括号。
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
参数的交互、借用的时间也会受到生命周期标注的影响:
fn main(){
let mut my_vec: Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec , &val1);
insert_value(&mut my_vec , &val2);
}
//错误,因为push需要保证value要活的比my_vec久,但在此函数内无法推断
fn insert_value(my_vec: &mut Vec<& i32> , value: & i32){
my_vec.push(value);
}
//错误,因为这会使得my_vec的可变引用被延迟到val1或val2被释放,导致多个可变引用同时存在
fn insert_value<'a>(my_vec: &'a mut Vec<&'a i32> , value: &'a i32){
my_vec.push(value);
}
//正确,让Vec与其值的生命周期独立开,只要求值见的生命周期同步。当然也要求Vec的值比Vec活得久
fn insert_value<'r , 'val>(my_vec: &'r mut Vec<&'val i32> , value: &'val i32){
my_vec.push(value);
}
//正确,无关的生命周期标注也可直接省略
fn insert_value<'val>(my_vec: &mut Vec<&'val i32> , value: &'val i32){
my_vec.push(value);
}
如果返回值的生命周期可以被推断,则可以省略。推断规则:
struct的所有引用变量都要进行生命周期标注。标注后,该变量的生命周期必须要长于struct。标注了生命周期的struct必须在impl的时候在impl后面也标注一个,即impl<'a> ST<'a>{...}
。
struct MyStruct<'a> {
my_ref: &'a i32,
}
impl<'a> MyStruct<'a> {
fn new(ref_to_i32: &'a i32) -> MyStruct<'a> {
MyStruct { my_ref: ref_to_i32 }
}
fn print(&self) {
println!("{}", self.my_ref);
}
}
fn main() {
let value = 5;
let my_struct = MyStruct::new(&value);
my_struct.print();
}
但如果impl里面并没有任何函数要用到生命周期标注的话,可以使用impl Mystruct<'_>{...}
。其他推荐显式标注生命周期、但完全可以由编译器推导的情况下,也可以使用'_
。
'static
是特殊的生命周期,表示整个程序的运行时间。如所有字符串字面值都有此生命周期。
让T成为'static
的子类,只有当T为引用的时候才要求必须是'static
,否则是有所有权的变量的话不做任何要求。
Subtyping and Variance - The Rust Reference
目的:让子可以完全处理父。
若有一个类型构造器Foo(Class)
,以类型为参数来产生另一个类型。设A是B的父类。
Foo(A)
与Foo(B)
无关,则称为不变(invariant)Foo(A)
是Foo(B)
的父类,则称为协变(covariant)Foo(A)
是Foo(B)
的子类,则称为逆变(contra-variant)原文:
F
is covariant over T
if T
being a subtype of U
implies that F
is a subtype of F
(subtyping “passes through”)F
is contravariant over T
if T
being a subtype of U
implies that F
is a subtype of F
F
is invariant over T
otherwise (no subtyping relation can be derived)不变实际上是要求A和B必须完全相同才能在转化后兼容。
若A 是B的父类,B是C的父类,则fn A => C
是fn B => B
的父类,因为参数为A的函数必然能处理参数B,而返回值为C的函数必然也能返回B(即fn A => C
无论是入参还是返回值都可以直接在外部视为fn B => B
)。因此fn类型对参数类型是逆变的,对返回值类型是协变的。
Type | Variance in 'a |
Variance in T |
---|---|---|
&'a T |
covariant | covariant |
&'a mut T |
covariant | invariant |
*const T |
covariant | |
*mut T |
invariant | |
[T] and [T; n] |
covariant | |
fn() -> T |
covariant | |
fn(T) -> () |
contravariant | |
std::cell::UnsafeCell |
invariant | |
std::marker::PhantomData |
covariant | |
dyn Trait |
covariant | invariant |
可见只有函数参数是逆变的;涉及可变的类型(而非生命周期)基本都是不变;剩下都是协变。
如果T不变,但T形如TypeName
,则X也不变。
struct Bar<'r>{
_phantom: PhantomData<fn(&'r ())>, //逆变套协变=逆变
}
fn bar<'short, 'long: 'short>(
mut short_bar: Bar<'short>,
mut long_bar: Bar<'long>)
{
//short_bar=long_bar; //编译不通过
long_bar=short_bar; //编译通过
}
//如果两个参数都改成mut short_bar: &mut Bar<'short>,就会导致short_bar和long_bar互相都不能赋值
下面是一个比较典型的例子:
struct Manager<'val>{
text: &'val str,
}
//不好的写法,因为&'a是协变,但<'a>是不变,因此Interface<'a>是不变
//这导致此结构体没有子类型,无法使其生命周期短于Manager<'a>
//struct Interface<'a>
// manager: &'a mut Manager<'a>
//}
//好的写法,解耦使'r保持协变,可以让Interface提前消亡而不影响Manager
struct Interface<'r, 'val>{
manager: &'r mut Manager<'val> //隐式定义'val: 'r
}
//这种解耦可以有效防止出现过长的、不可测的借用
//因此生命周期标注可以看做是缩短、切割、独立而不是延长、接续、衔接
struct List<'val>{
manager: Manager<'val>,
}
impl<'val> List<'val>{
//解耦了:1.该方法对self的借用;2.self本身(所有权变量)的生命周期
//相当于要求这个借用一直持续到Interface消亡(由于'val: 'r所以不用管'val)
pub fn get_interface<'r>(&'r mut self) -> Interface<'r, 'val> {
Interface{
manager: &mut self.manager,
}
}
//也可以省略'r,让编译器自己推断
pub fn get_interface_2(&mut self) -> Interface<'_, 'val> {
Interface{
manager: &mut self.manager,
}
}
}
fn main(){
let mut list = List{
manager: Manager{
text: "abc",
}
};
let i1 = list.get_interface();
println!("1: {}",i1.manager.text);
let i2 = list.get_interface();
println!("2: {}",i2.manager.text);
//然而下面这个是过不了的,因为i1这个interface还没消亡,因此对list的借用没结束
//let i1 = list.get_interface();
//let i2 = list.get_interface();
//println!("1: {}",i1.manager.text);
//println!("2: {}",i2.manager.text);
}
&T实现了Copy,但&mut T没有
下面的代码中,let y: &mut i32 = x;
并不会发生Move,而是等价于let y: &mut i32 = &mut *x;
,将x解引用后再次创建可变引用,即reborrow。当y消亡后,x又可以继续使用。如果提前使用x,就会提示x已经被借用了;也就是说,reborrow相当于对引用进行一次借用。
fn main(){
let mut i = 42;
let x = &mut i;
let y: &mut i32 = x;
//y对x重引用后,y成为了i的唯一可变引用
*y=43;
println!("y {}",*y);
//此处y消亡,x重新变成i的唯一可变引用
*x=44;
println!("x {}",*x);
}
实际上,将一个可变引用传参到func_name(val_name: &mut T)
也是一次重引用。
&'b &'a StructName
即为&'b (&'a StructName)
。解引用时先把'b
给解了。
&'b &'a StructName
=>'a: 'b
,因为&'a StructName
作为一个&T
收到了'b &T'
的制约,因此'a
不得短于'b
。
*rb
产生'c
,'c: 'b
,因为'c
是&'b &'a
解引用出来的,因此&'b &'c
也要合法。
可以简单记忆成返回
'a
和'b
中生命周期短的那个。
//返回为&'b时,参数无论怎样mut都是合法的,证略
//返回的'b并不是指'b层的引用,而是返回的'c: 'b,因此返回'b必然合法
fn func_name<'b, 'a>(rb: &'b &'a StructName) -> &'b StructName {
*rb
}
//返回为&'b mut时,仅当参数为&'b mut &'a mut时才是合法的,实际上是进行一次reborrow
//&'b &'a mut是不行的,因为&'b不可变,可以拷贝多份,因此显然不能随意拿出它里面的可变引用
fn func_name<'b, 'a>(rb: &'b mut &'a mut StructName) -> &'b mut StructName {
*rb
}
//返回为&'a时,参数只能是&'b &'a或&'b mut &'a,即要求参数的&'a不能是mut,以进行copy
fn func_name<'b, 'a>(rb: &'b mut &'a StructName) -> &'a StructName {
*rb
}
//返回为&'a时,参数是什么都不行,只有如下情况可以使用
fn func_name<'b: 'a, 'a>(rb: &'b mut &'a mut StructName) -> &'a StructName {
*rb
}
涉及到高阶trait绑定。
下面这段代码无法通过编译,因为如果在函数上定义了'a
,则'a
默认比函数体要长,因此不接纳zero。
fn call_on_ref_zero<'a, F>(f: F)
where
F: Fn(&'a i32),
{
let zero = 0;
f(&zero);
}
将代码改成如下形式即可:
fn call_on_ref_zero<F>(f: F)
where
F: for <'a> Fn(&'a i32), //表示类型参数F可以接纳任意的'a
//F: Fn(&'a i32) 也可以直接这样写,因为Fn本身就是高阶的生命周期绑定。
{
//zero的生命周期依然只有这两行
let zero = 0;
f(&zero);
}
可能失败的方法会返回Result,是个枚举类,为Ok(val)
或Err(_)
,可以交由expect处理(result.expect(msg)
)。若Result为Ok,则返回Result中存储的结果值val;否则,打印expect的参数msg并停止程序。
匹配到符合的结果,执行其后的语句,且可以有返回值。分支称为arm。
编译时要求必须处理所有情况。使用_
通配符或者用一个变量接住即可匹配剩余所有情况。
let value = 5;
match value {
1 => println!("one") //arm
2 => {...}
_ => {...}
}
match value {
1 => println!("one"),
2 => println!("two"),
//用变量接住
other => println!("The value is: {}", other),
}
match guess.cmp(&secret_number){
Ordering::Less=>println!("Too small!"),
Ordering::Greater=>println!("Too big!"),
Ordering::Equal=>println!("You win!"),
}
对于枚举类型、结构体、Tuple等等,也可以在模式匹配的时候将其绑定的值赋值到变量中。即使存在嵌套,也可以解构成功。只想占位的话使用_
来防止发生不必要的所有权转移。
let guess: u32 = match str.trim().parse(){
OK(num)=>num*2,
Err(_)=>{
println!("You Wrong!"),
println!("OOPS"),
}
};
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("The Quit variant was passed in.");
}
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {}, green {}, and blue {}", r, g, b);
}
Message::Move { x, y: new_y } => {
println!("Move in the x direction {} and in the y direction {}", x, new_y);
}
Message::Write(text) => {
println!("Text message: {}", text);
}
}
}
在箭头之前加上if就能进一步判断以进行过滤:
fn main() {
let x = 5;
match x {
// 如果x是1、2或3时,打印"Small"
1 | 2 | 3 if is_even(x) => println!("Small and even"),
1 | 2 | 3 => println!("Small"),
// 如果x是4到10之间的偶数,打印"Medium and even"
y @ 4..=10 if is_even(y) => println!("Medium and even"),
// 如果x是4到10之间的数,打印"Medium"
4..=10 => println!("Medium"),
// 其他情况,打印"Large"
_ => println!("Large"),
}
}
fn is_even(num: u32) -> bool {
num % 2 == 0
}
当要操作的情况只有一个,其他情况都要忽略时,就能用if let简化:
let option = Some(5);
if let Some(5) = option {
println!("Five");
} else if let Some(6) = option {
println!("Six");
} else {
println!("Not five or six");
}
和if的区别是,if let使用了match的模式匹配,可以提取绑定值。可以搭配else、else if使用。
if let可以和普通if语句(包括else if)混写,只是带let的判断会利用模式匹配。
切片是对数组型数据结构的一部分的引用,不具有所有权。字符串切片类型记为&str
。
let mut s = String::from("abcdef");
let s1=&s[2..4];
let s2=&s[2..=4];
println!("slice: {} {}",s1,s2);
//输出:slice: cd cde
//即Range包含的数字都会被提取
//可以省略前置0或后置字符串长度。因此&str[..]即是对整个数组的切片
对i32
字符串的切片类型为&[i32]
。
使用len()
方法可以获得切片的元素数量。
String是Byte的集合,采用UTF-8编码,是在标准库中的。本质是对Vec
的封装。
字符串字面值(本质是字符串切片,不可变引用)是硬编码的,访问快但本身不可变。其对应的变量就是个字符串切片,是个不可变引用,类型为&str
。&str
转String类型要使用String::from
。
只有&str
可以转String
,反之不行。
let mut str = String::from("abc");
str.push_str("def");
let mut str1 = "abc1".parse().unwrap();
加法运算合并字符串时,只有第一个参数可以是String:
let s1 = "Hello";
let s2 = "World";
let merged = s1 + s2;
let s11 = String::from("Hello");
let s22 = String::from("World");
let mergedd = s1 + &s2;
函数参数如果是字符串的话,最好使用&str
类型来表示参数,即强制要求传切片。这使得在传参为切片的时候可以直接调用,传参为String的时候则要求创建完整切片(Rust会自动转换)后传入,更加通用了。这也可以使得字符串传参没有任何的副作用。
使用+
即可连接两个String。但+
本质还是调用函数,因此如果let s=s1+&s2
,会导致调用完毕后s2还能接着用,但s1的所有权被剥夺了。
formit!
用法类似于println!
,可以用于字符串拼接:
let name = "Alice";
let age = 30;
let s = format!("{} is {} years old.", name, age);
println!("{}", s);
不支持索引访问字符。因为UTF-8编码下某些字符会占多个字节,难以识别。因此直接调用len方法返回的是占用的字节数,也就不一定等于字符个数。
字符串切片的时候,切片的索引是按byte来的。如果切片的索引不是char的边界(相当于把字符给分裂了),则会panic。
fn string_slice(arg: &str) {
println!("{}", arg);
}
fn string(arg: String) {
println!("{}", arg);
}
fn main() {
string_slice("blue");
string("red".to_string());
string(String::from("hi"));
string("rust is fun!".to_owned());
string("nice weather".into());
string(format!("Interpolation {}", "Station"));
string_slice(&String::from("abc")[0..1]); //切片
string_slice(" hello there ".trim()); //去掉前后缀空白
string("Happy Monday!".to_string().replace("Mon", "Tues")); //文本替换
string("mY sHiFt KeY iS sTiCkY".to_lowercase()); //大小写替换
}
let v1: Vec<i32> = Vec::new();
let v2 = vec![1,2,3];
//泛型可以通过第一个插入值来自动推断
let mut v = Vec::new();
v.push(1); //自动推断出i32
可以使用索引和get两种方式访问值。但是索引如果越界,会直接报错;get会返回Option
,如果越界了会返回None:
let v = vec![1, 2, 3, 4, 5];
match v.get(2) {
Some(value) => println!("The third element is {}", value),
None => println!("There is no third element."),
}
遍历vector:
let mut v = vec![2,5,7];
for i in &mut v {
*i += 50;
}
使用附带数据的枚举可以实现用vector存储不同类型的数据。编译器推导出vector的泛型是枚举后,就相当于得知了它的有限个类型种类,也就可以生成对应的操作应对方案。
enum Data {
Integer(i32),
Float(f64),
Text(String),
}
fn main() {
let data = vec![
Data::Integer(10),
Data::Float(3.14),
Data::Text(String::from("Hello")),
];
for item in &data {
match item {
Data::Integer(i) => println!("Integer: {}", i),
Data::Float(f) => println!("Float: {}", f),
Data::Text(t) => println!("Text: {}", t),
}
}
}
相当于unordered_map。
不在预导入模块中,需要use std::collections::HashMap
。
可以使用collect来从形如(k,v)的Tuple数组中构造HashMap。collect方法适用于很多集合,因此需要给返回值显式指定类型HashMap<_, _>
。
use std::collections::HashMap;
let vec = vec![("key1", 1), ("key2", 2), ("key3", 3)];
let map: HashMap<_, _> = vec.into_iter().collect();
println!("{:?}", map);
//利用zip可以从两个vector构造HashMap
use std::collections::HashMap;
let keys = vec!["key1", "key2", "key3"];
let values = vec![1, 2, 3];
let map: HashMap<_, _> = keys.into_iter().zip(values.into_iter()).collect();
println!("{:?}", map);
HashMap直接传值会剥夺所有权。传引用的话要注意数据不能提前失效。
遍历:
for (k,v) in &map {
...
}
insert同一个key会导致覆盖。可以用contains_key()
来检测一个key是否存在于HashMap中
entry(key)方法可以返回一个枚举Entry,是key对应的值的入口,可以用于表示key是否存在。Entry有or_insert(value)方法,如果key存在则返回key对应的值的可变引用,如果不存在则插入参数value然后返回对应的值的可变引用。
默认的HashMap安全但不是最快的,可以替换hasher。
中的T是类型参数。在编译时会被替换为具体的类型,这个过程称为单态化(monomorphyzation)。
在函数名、结构体名或枚举名后面加个
就表示了泛型。
使用泛型时使用StructName::
来指明T的具体类型TypeName,也可以自动推断。
struct Point<T> {
x: T,
y: T,
}
fn main() {
//自动推断
let integer = Point { x: 5, y: 10 };
//手动指定
let float = Point::<f32> { x: 1.0, y: 4.0 };
println!("integer: ({}, {}), float: ({}, {})", integer.x, integer.y, float.x, float.y);
}
如果要写泛型结构体的impl,则要写成impl
。但针对具体类型的实现就不需要这样写了,如impl Point
。(要尽量早地“注册”所有符号名)
泛型T的约束加在impl的后面。
//这个例子中,ReportCard::不需要指明具体类型,可自动推断,即不用写::
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
impl<T: std::fmt::Display> ReportCard<T> {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard::<f32> {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1"
);
}
#[test]
fn generate_alphabetic_report_card() {
let report_card = ReportCard::<String> {
grade: "A+".to_string(),
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+"
);
}
}
trait告诉编译器某种类型有哪些可以与其他类型共享的功能。抽象的定义共享行为。
trait bound(约束): 让泛型的类型参数指定为一个trait,即可规定该类型参数的行为,也就能让以该类型参数为类型的变量能做出一些事情,如比大小。
trait和interface差不多,规定了共用的一组方法签名,但可以没有实现;如果有实现,就相当于是默认实现(java的default);实现trait的类型必须实现所有未实现的trait方法。
默认实现的方法可以调用其他无默认实现的方法。
trait Speak {
fn speak(&self);
}
struct Human;
impl Speak for Human {
fn speak(&self) {
println!("Hello, world!");
}
}
fn main() {
let person = Human;
person.speak();
}
如果一个方法来自于trait,则使用的时候必须保证当前作用域里面有此trait。
参数的mut与否似乎并不是严格限制的:
trait AppendBar {
fn append_bar(self) -> Self;
}
impl AppendBar for String {
fn append_bar(mut self) -> Self{
self.push_str("Bar");
self
}
}
可以在某个type上实现某个trait的前提条件是:这个type 或 这个trait 是本crate里定义的。也就是说,无法为外部的类型定义外部的trait。这样可以保证不会出现两个crate分别给同一个struct实现同一个trait的情况。即孤儿规则(Orphan Rule),防止trait的实现是个孤儿(既不属于定义trait的crate,也不属于定义type的crate)。但也因此可能无法做到为某些外部结构体实现Debug、Display,再套一层结构体即可解决:
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);
}
trait可以作为参数,当成接口类型使用:
pub trait Animal {
fn speak(&self);
}
pub struct Dog;
pub struct Cat;
impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}
impl Animal for Cat {
fn speak(&self) {
println!("Meow!");
}
}
//impl trait 写法 (是trait bound 写法的语法糖)
fn make_animal_speak1(animal: &impl Animal) {
animal.speak();
}
//dyn trait 写法(动态分发)
fn make_animal_speak1(animal: &impl Animal) {
animal.speak();
}
//trait bound 写法
fn make_animal_speak2<T: Animal>(animal: &T) {
animal.speak();
}
fn main() {
let dog = Dog;
let cat = Cat;
make_animal_speak1(&dog);
make_animal_speak2(&cat);
}
也可以使用+
规定参数需要同时实现多个trait:
fn action<T: Speak + Move>(animal: T) {
animal.speak();
animal.move();
}
可以用where
子句来指定类型参数的约束,以保证函数名到函数参数列表之间不会写太长的东西:
fn some_function<T, U>(t: T, u: U) -> i32
where
T: std::fmt::Display + Put,
U: std::fmt::Debug,
{
println!("T: {}", t);
println!("U: {:?}", u);
42
}
可以直接-> impl Speak
,但是函数内的具体的返回值不能为多个类型,即使它们都实现了Speak这个trait。可以理解为编译器会根据具体的返回值去推断返回值类型,推断成功后才又和规定的impl xxx进行对比以确保那唯一的返回值类型实现了该xxx trait。
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
覆盖实现(blanket implementation): 在impl后面对类型参数使用trait bound进行约束,再把for后面的type改成类型参数T。这样的话,满足trait bound约束的type都会被这个impl覆盖实现。
impl<T: Display> ToString for T {
// ...
}
在trait定义一个type,具体使用时将其赋值后即可关联到某个类型,如:
trait Iterator {
type Item; // 关联类型,表示迭代器的元素类型
fn next(&mut self) -> Option<Self::Item>; // next方法返回关联类型的Option值
}
使用关联类型而不使用泛型的话无法给一个类型多次实现一个trait。
重载std::ops
里的trait就等价于运算符重载。
//重载了Meters对Millimeters的+运算符
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + other.0 * 1000)
}
}
如果结构体Human实现了Pilot trait,也实现了Wizard trait,且Human自己、两个trait内都实现了不同的fly方法,则:
let person = Human;
person.fly(); //调用原来的
Pilot::fly(&person); //调用Pilot trait的实现
Wizard::fly(&person); //调用Wizard trait的实现
这种情况下,比如对Pilot这个trait来说,由于接收到了&person为&Human,则可以推断出是调用Human对Pilot的实现中的fly。
但是也有情况是推断不出来的,例如要调用的不是方法而是函数,而且参数很朴素:
Dog::baby_name(); //调用原来的
Animal::baby_name(); //Error: 无法确认到底调用哪个函数
<Dog as Animal> :: baby_name(); //调用Animal trait的实现
就叫做完全限定语法。
即trait的继承,trait TraitName: SuperTraitName{...}
即可使用SuperTraitName的所有特性。
智能指针Box
逻辑上没问题,但已被弃用,应当使用Box
。
fn main() {
// 定义一个闭包,它接受两个i32类型的参数,返回它们的乘积
let multiply = |x: i32, y: i32| -> i32 {
let result = x * y;
println!("The product of {} and {} is {}", x, y, result);
result
};
// 调用闭包
let product = multiply(4, 5);
println!("Product: {}", product);
}
闭包不要求显式指定参数和返回值的类型,可以依靠编译器进行上下文推断。
表达式只有单个的时候可以省略花括号,比如let add_x = |y| y + x;
。
每个闭包类型都有独特的类型,即使它们长得完全一样。
如果想让一个复杂运算只执行一次(延迟计算),则可以维护一个结构体,里面含有一个闭包,和一个存储参数->返回值的HashMap。
闭包至少实现了下面三个trait中的一个,且实现了前面的之后就必然也实现了所有后面的:
一个闭包可以被struct STName
使用。
在参数列表前使用move关键字,就可以强制获取所使用的所有环境值的所有权。可用于将闭包传递给新线程并把数据都给新线程使用的时候。
由于闭包是trait,是动态的,无法直接作为变量类型、返回值等,但可以使用Box
。
函数指针类型为fn(i32)->i32
。其中fn为类型而非trait。
函数指针必然同时实现了Fn、FnMut、FnOnce三个trait。因此函数指针也能当闭包用。
对一个数组的每一项都做同一件事的时候,我们可以用map然后传入一个闭包,也可以传入一个函数指针,此时遍历到的项会作为参数传入函数:
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> =
list_of_numbers.iter().map(|i| i.to_string()).collect();
let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();
在带数据的枚举中,构造枚举的构造器也是个函数,因此也能传入map:
enum Status {
Value(u32),
Stop,
}
//变为Status::Value数组
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
迭代器实现了Iterator trait。需要指定type Item并实现一个next方法。next返回None意味着迭代结束。
迭代器循环遍历会做循环展开优化。
pub trait Iterator {
type Item; //迭代器的返回类型
fn next(&mut self) -> Option<Self::Item>; //遍历方法
// methods with default implementations elided
}
iter
: 遍历的时候获取到的都是不可变引用。iter_mut
: 遍历的时候获取到的都是可变引用。into_iter
: 遍历的时候会获取所有权。next方法本质是对迭代器的消耗(因为不能遍历第二遍)。
Iterator trait中有一些带默认实现的方法,如果方法实现里面调用了next,就称为消耗型适配器,如sum方法。还有些方法将迭代器转化为不同的迭代器,称为迭代器适配器,如map方法。
如果next不配调用(或者说没有消耗型适配器被调用)的话,迭代器不会做任何事,包括调用的迭代器适配器。
Iterator in std::iter - Rust
迭代器函数可能是lazy的,不一定会立刻执行!
let a = [1, 2, 3];
//map对所有元素作某事后,返回新的迭代器
//因此闭包的传入与输出类型不同时可以实现类型转换
let mut iter = a.iter().map(|x| 2 * x);
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
//使用for_each可以用闭包替代for循环
//不返回新东西,且立即执行
(0..5).flat_map(|x| x * 100 .. x * 110)
.enumerate()
.filter(|&(i, x)| (i + x) % 3 == 0)
.for_each(|(i, x)| println!("{i}:{x}"));
let numbers = vec![10, 20, 30, 40, 50];
//enumerate为迭代值标上索引号
for (index, number) in numbers.iter().enumerate() {
println!("Index: {}, Number: {}", index, number);
}
//剔除不小于maxLen的元素
dataList.into_iter().filter(|x| -> x.dataLen < maxLen).collect();
//阶乘
pub fn factorial(num: u64) -> u64 {
//acc: accumulator, e: element
//初始时acc=第一个参数,然后和所有元素依次执行闭包
(1..=num).fold(1, |acc, e| acc * e)
//如果第一个参数完全可以当成迭代器的第一个元素的话,可以改用reduce,但要求迭代器不为空
}
let a = [0, 1, 2, 3, 4, 5];
//step_by改变next的行为
let mut iter = a.iter().step_by(2);
assert_eq!(iter.next(), Some(&0));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&4));
assert_eq!(iter.next(), None);
type newTypeName = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;
type Result<T> = Result<T, std::io::Error>;
type Result<T> = std::io::Result<T>;
!
即为Never类型,可以被转换为其他任意类型。并不是void,不返回并不是Never。
动态大小类型(DST)只有在运行时才能知道占多少内存空间,且一旦确定就不能再变化。
比如str类型,如果用str类型赋值了长为3的字符串,就不能再用于长为4的字符串。
所有trait都是动态大小类型,要么使用引用来访问,要么放在指针里面。
Sized trait: 要求编译时已知大小的类型都会隐式地实现这个trait。泛型函数都会隐式添加该trait bound,因此泛型函数的实现类型必须是编译时已知大小的,但可以加?Sized trait
来解除限制。
?Sized trait: 表示T可能是也可能不是确定大小的,可以强制让泛型函数使用动态大小类型。
最简单的智能指针,指向堆上的内存,没有开销或额外功能。
使用Box::new(value)
创建Box指针,且会转移所有权。
deref trait里面有fn deref(&self) -> &T
。
解引用符号*y
作用于实现了deref trait的类型的时候,会自动变成*(y.deref())
,即隐式解引用转化(Deref Coercion)。
在函数传递的参数类型不匹配的时候,编译器会试图调用deref来进行转换。如&String
就有可能会转换成&str
。
当T:Deref
时,允许&T
或&mut T
转换为&U
。
当T:DerefMut
时,允许&mut T
转换为&mut U
。
drop trait里面有fn drop(&mut self)
。会在变量离开作用域时自动调用,被当做析构函数。不能直接地显式调用,但可以调用std::mem::drop(value)
,以提前释放变量(调用drop)。
引用计数(Reference Count)的智能指针,可共享数据。只能用于单线程。不在预导入模块,在std::rc::Rc
。
Rc全都是不可变引用,用于共享只读,因为有多个可变引用的话是违背借用规则的。
Rc::new(value)
创建Rc指针,且会转移所有权。
Rc::clone(&rc_pointer)
可以复制一个Rc指针,令引用计数器+1。
rc_pointer.clone()
似乎完全等价于Rc::clone(&rc_pointer)
,但语义上有歧义。
Rc::strong_count(&rc_pointer)
会返回引用计数。
为了防止循环引用导致引用计数永远不为0、因此出现内存泄漏的情况,可以使用Rc::downgrade(&rc_pointer)
来创建弱引用的智能指针Weak
,并使弱引用计数+1。
Rc::weak_count(&rc_pointer)
会返回弱引用计数。
弱引用计数不为0也不影响数据清理,当强引用计数为0的时候,弱引用会自动断开。
弱引用有方法weak_pointer.upgrade()
,返回Option
,以检测数据是否被清理了,若没有被清理则返回强引用。
fn main() {
let strong = Rc::new("Hello, world!".to_string());
let weak = Rc::downgrade(&strong);
if let Some(weak_ref) = Weak::upgrade(&weak) {
// 强引用还存在,可以安全地访问
println!("Strong: {}", *weak_ref);
} else {
// 强引用已断开
println!("Strong reference has been dropped.");
}
}
RefCell指针支持可变引用和不可变引用,而其安全检查在运行时;拥有数据的所有权,不共享。不在预导入模块,在std::cell::RefCell
。即使是不可变引用,也能修改其值。
内部可变性(interior mutability) 可变地借用一个不可变的值,从而允许在持有不可变引用的情况下修改数据。指针本身对外是不可变的数据,但依旧提供接口来改变内部数据。
方法(安全接口):
borrow()
: 返回Ref
(不可变),实现了deref。borrow_mut()
: 返回RefMut
(可变),实现了deref。因为实现了deref,因此borrow到了之后解引用即可直接使用。
会分别对borrow出的Ref和RefMut进行引用计数,以此保证运行时满足借用规则(只有多个不可变借用,或只有一个可变借用)。
使用Rc
可以使得数据可以被多方共享且能修改,只要保证读写完之后引用的RefCell
马上消失即可。
通过复制而非借用来访问数据,有内部可变性。
使用Cow::from(xxx)
来对xxx建立Clone-On-Write的访问机制。当Cow不具有xxx的所有权时,其类型为Cow::Borrowed(xxx)
;具有所有权时为Cow::Owned(xxx)
。可以使用is_borrowed()
和is_owned()
或者模式匹配来判断其类型。
使用to_mut()
来获得可变引用。如果此时未拥有所有权,则对值进行复制,变成Cow::Owned(xxx)
。
use std::borrow::Cow;
fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// Clones into a vector if not already owned.
input.to_mut()[i] = -v;
}
}
input
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reference_mutation() -> Result<(), &'static str> {
// Clone occurs because `input` needs to be mutated.
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn reference_no_mutation() -> Result<(), &'static str> {
// No clone occurs because `input` doesn't need to be mutated.
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]);
match abs_all(&mut input) {
Cow::Borrowed(_) => Ok(()),
_ => Err("Expected borrowed value"),
}
}
#[test]
fn owned_no_mutation() -> Result<(), &'static str> {
// We can also pass `slice` without `&` so Cow owns it directly. In this
// case no mutation occurs and thus also no clone, but the result is
// still owned because it was never borrowed or mutated.
let slice = vec![0, 1, 2];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn owned_mutation() -> Result<(), &'static str> {
// Of course this is also the case if a mutation does occur. In this
// case the call to `to_mut()` returns a reference to the same data as
// before.
let slice = vec![-1, 0, 1];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
}
类似于Rc
use std::sync::Arc;
use std::thread;
fn main() {
let numbers: Vec<_> = (0..100u32).collect();
let shared_numbers = Arc::new(numbers);
let mut joinhandles = Vec::new();
for offset in 0..8 {
let child_numbers = shared_numbers.clone();
joinhandles.push(thread::spawn(move || {
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
println!("Sum of offset {} is {}", offset, sum);
}));
}
for handle in joinhandles.into_iter() {
handle.join().unwrap();
}
}
用于实现跨线程情形下的内部可变性模式,有内部可变性。
结合Arc使用即可进行线程安全的读写共享:
use std::sync::{Arc, Mutex};
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 || {
//对Mutex进行lock后unwrap就能获得值,并且在离开作用域后自动解锁
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
用于类型转换和import的重命名。
From in std::convert - Rust
实现了From就能使用into()
来转换类型:
// Use the `from` function
let p1 = Person::from("Mark,20");
// Since From is implemented for Person, we should be able to use Into
let p2: Person = "Gerald,70".into();
// Obtain the number of bytes (not characters) in the given argument.
// TODO: Add the AsRef trait appropriately as a trait bound.
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
arg.as_ref().as_bytes().len()
}
// Obtain the number of characters (not bytes) in the given argument.
// TODO: Add the AsRef trait appropriately as a trait bound.
fn char_counter<T: AsRef<str>>(arg: T) -> usize {
arg.as_ref().chars().count()
}
// Squares a number using as_mut().
// TODO: Add the appropriate trait bound.
fn num_sq<T: AsMut<u32>>(arg: &mut T) {
// TODO: Implement the function body.
*arg.as_mut() *= *arg.as_mut();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn different_counts() {
let s = "Café au lait";
assert_ne!(char_counter(s), byte_counter(s));
}
#[test]
fn same_counts() {
let s = "Cafe au lait";
assert_eq!(char_counter(s), byte_counter(s));
}
#[test]
fn different_counts_using_string() {
let s = String::from("Café au lait");
assert_ne!(char_counter(s.clone()), byte_counter(s));
}
#[test]
fn same_counts_using_string() {
let s = String::from("Cafe au lait");
assert_eq!(char_counter(s.clone()), byte_counter(s));
}
#[test]
fn mult_box() {
let mut num: Box<u32> = Box::new(3);
num_sq(&mut num);
assert_eq!(*num, 9);
}
}
Rust标准库只提供1:1模型,即通过调用OS API来创建线程,有较小的运行时(宿主环境),可以方便与C交互。
使用std::thread::spawn
传入一个闭包即可创建一个新线程,返回值为一个handle,对handle调用join即可让当前线程阻塞直到handler其完成。
use std::thread;
use std::time::Duration;
fn main() {
let handler = 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));
}
handler.join().unwrap();
}
如果线程需要获得某个变量的所有权,则在闭包前面加上move
。这样的话,直到该线程结束之前,其他线程都不能使用此变量。
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
使用std::sync::mpsc::channel()
即可创建一个channel,返回元组(发送端tx,接收端rx)
。
mpsc = mutiple producer, single consumer
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
使用try_recv()
方法即可不阻塞地尝试接收一次,如果接收失败则返回Err。
对发送方tx进行clone即可建立多个发送方。
macro_rules!
构建的声明宏(declarative macros)(弃用)。#[derive]
宏,只能用于struct或enum,可以为其指定随derive属性添加的代码。宏定义前的代码无法使用此宏,要注意宏定义顺序。
用macro_rules!书写的宏的语法类似于match语句。定义时名字不用加感叹号。
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
fn main() {
my_macro!();
}
in this situation, the value is the literal Rust source code passed to the macro; the patterns are compared with the structure of that source code; and the code associated with each pattern, when matched, replaces the code passed to the macro. This all happens during compilation
The
#[macro_export]
annotation indicates that this macro should be made available whenever the crate in which the macro is defined is brought into scope. Without this annotation, the macro can’t be brought into scope.
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
mod macros {
#[macro_export]
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}
可以有多个分支情况:
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
($val:expr) => {
println!("Look at this other macro: {}", $val);
};
}
fn main() {
my_macro!();
my_macro!(7777);
}
写出#[derive(SomeName)]
后,会匹配标注了#[proc_macro_derive(SomeName)]
的函数,并把#[derive(SomeName)]
标注的代码段作为输入传入,生成的输出会贴到#[derive(SomeName)]
所在的地方的下面。
#[route(GET,"/")]
fn index(){
...
}
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
...
}
和derive一样进行匹配和传参,但会额外传TokenStream,即GET, "/"
。
let sql = sql!(SELECT * FROM posts WHERE id=1);
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
...
}
把括号里面的字符都传到TokenStream,进行处理。可以看做其参数可无视rust语法限制的函数。
被unsafe修饰的代码块允许:
原始指针:
*mut T
*const T
(不能通过它对其所指的数据赋值)目的:
可变指针不算引用,因此可变不可变不受借用规则约束,也因此打破了借用规则(但直接用引用的话依旧无法打破)。
可能空指针、野指针,也不自动释放。
在安全代码块里面也能定义原始指针,但禁止解引用。
let mut num = 5;
let r1 = &num as *const i32;
let address = 0x012345usize;
let r2 = &num as *mut i32;
let slice: &mut [i32] = ...;
let r3 = slice.as_mut_ptr();
/// # Safety
///
/// The `ptr` must contain an owned box of `Foo`.
unsafe fn raw_pointer_to_box(ptr: *mut Foo) -> Box<Foo> {
// SAFETY: The `ptr` contains an owned box of `Foo` by contract. We
// simply reconstruct the box from that pointer.
let mut ret: Box<Foo> = unsafe { Box::from_raw(ptr) };
ret.b = Some("hello".to_owned());
ret
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn test_success() {
let data = Box::new(Foo { a: 1, b: None });
let ptr_1 = &data.a as *const u128 as usize;
// SAFETY: We pass an owned box of `Foo`.
let ret = unsafe { raw_pointer_to_box(Box::into_raw(data)) };
let ptr_2 = &ret.a as *const u128 as usize;
assert!(ptr_1 == ptr_2);
assert!(ret.b == Some("hello".to_owned()));
}
}
在安全的函数里面可以使用unsafe代码块,因此要在unsafe代码块的外面确保unsafe是safe的。这个必须人为保证。
定义trait的时候加上unsafe,则它只能在unsafe的代码块中实现,一般直接unsafe impl
。
Unsafe Rust - The Rust Programming Language
External blocks - The Rust Reference
extern简化创建和使用外部函数接口(FFI,foreign function interface)的过程。
使用类似extern "C" {...}
可以声明外部的不同语言的函数;"C"
就是ABI(application binary interface),通过指定汇编规则来指定语言。extern代码块里的所有东西都是unsafe的。
在fn前加上extern "C"
即可创建接口供其它语言调用。若不加则等价于extern "Rust"
。添加#[no_mangle]
注解可以防止编译期间发生名称更改。
extern "Rust" {
fn my_demo_function(a: u32) -> u32;
#[link_name = "my_demo_function"]
fn my_demo_function_alias(a: u32) -> u32;
}
mod Foo {
// No `extern` equals `extern "Rust"`.
#[no_mangle]
fn my_demo_function(a: u32) -> u32 {
a
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success() {
// The externally imported functions are UNSAFE by default
// because of untrusted source of other languages. You may
// wrap them in safe Rust APIs to ease the burden of callers.
//
// SAFETY: We know those functions are aliases of a safe
// Rust function.
unsafe {
my_demo_function(123);
my_demo_function_alias(456);
}
}
}
文档注释也分为单行注释和块注释,但又有内外之分:
二者的区别:
#[doc="..."]
是等价的。#![doc="..."]
是等价的。另外,在文档注释中可以使用 Markdown 语法。
使用#[derive(Debug)]
派生宏,使得ST由Debug trait派生;打印时使用{:?}
即可行内打印,使用{:#?}
即可换行打印。这样可以完整地打印结构体或枚举。
#[derive(Debug)]
struct ST{
v1: i32,
v2: f64,
}
let st = ST{
v1: 1,
v2: 3.5,
};
println!("{:?}",st)
println!("{:#?}",st)
//输出:
// ST { v1: 1, v2: 3.5 }
// ST {
// v1: 1,
// v2: 3.5,
// }
//use rand::Rng;
let mut rng = rand::thread_rng();
let random_number: u32 = rng.gen_range(0,5); //生成[0,5)的随机数
println!("random_num: {}", random_number);
let chars = vec!['H', 'e', 'l', 'l', 'o'];
let string: String = chars.into_iter().collect();
不要使用String的push,因为String每次push都会重新分配内存,而vector的容量是指数倍增长的,重新分配的次数少。
fn func(&[i32]){...}
fn main(){
func(&[2,3,4]);
}
match xxx {...}
在xxx
处的返回结果不会发生拷贝,导致里面的某些引用被延长到match结束。因此复杂的xxx
取值最好在外面赋给一个变量然后再match。在if等语句中应该都会出现。
有时候一个结构体要销毁了,但它的成员变量还有用,又不好使用智能指针,也不应当clone时,可以考虑将其类型设为Option
,要Move走时就进行obj.variable_name.take().unwrap()
将T
取出。
#[derive(Debug, PartialEq, Eq)]
struct A {
x: i32,
y: i32,
}
impl A {
/// 这个方法将self这个可变引用的生命周期转移到了字段x上。
///
/// 这种函数相当于在这个生命周期范围内,对该结构体的
/// 可访问范围从整个结构体**缩小**到了其中的某个字段上,
/// 而舍弃了在这个生命周期内访问其他字段的权利。
fn x(&mut self) -> &mut i32 {
&mut self.x
}
/// 同理同上
#[allow(unused)]
fn y(&mut self) -> &mut i32 {
&mut self.y
}
/// 这个方法将self可变引用的生命周期**拆分并分别转移**到了x和y上。
fn x_and_y(&mut self) -> (&mut i32, &mut i32) {
(&mut self.x, &mut self.y)
}
}
fn main() {
let mut a = A {
x: 1,
y: 2,
};
{
let (x, y) = a.x_and_y();
*x += 2;
*y += 2;
assert_eq!(a, A { x: 3, y: 4 });
}
{
// 这里使用了另一种方法:结构化绑定。这种语法是模式匹配的一种,与在match中列举enum的各种枚举值的表达式是同一种语法。
let A { x, y } = &mut a;
*x += 2;
*y += 2;
assert_eq!(a, A { x: 5, y: 6 });
match &mut a {
A { x: 5, y } => assert_eq!(y, &mut 6),
_ => panic!("x != 5"),
}
match &mut a {
// 忽略了y。作用相当于通配符“_”。
A { x, .. } => assert_eq!(x, &mut 5),
}
}
// 错误用法。
{
let x = a.x();
// let y = a.y();
*x += 2;
// *y += 2;
}
}
按层级从高到低为:
Package包含:
在Crate.toml里的dependencies下可以添加外部包,以到https://crates.io/
下载包。
std标准库是一个自动导入的外部包。
Crate将相关功能组合到一个作用域内用于共享,避免冲突。
Crate类型:
Crate Root: 是个源代码文件,Cargo将它交给编译器,编译器由此开始构建Crate,组成Crate的根Module。
Crate默认:
binary crate可视为独立运行,无法共享功能给其他crate,也就无法写集成测试。一般逻辑都写在library crate里面,然后由简单的binary crate调用。
Module在一个Crate内将代码进行分组,增加可读性,同时控制项目(item)的私有性(private,public)。
Module可以嵌套,包含子Module。
子mod默认无法被上级发现,若想暴露需要使用pub mod
。
使用mod关键字来定义模块:
mod outer {
pub mod inner {
pub fn inner_function() {
println!("This is an inner function!");
}
}
}
fn main() {
outer::inner::inner_function();
}
私有边界(Privacy Boundary):默认所有条目(包括子模块)都是私有的,在条目或子模块前面加pub后才能被其他模块发现。父不能访问子的私有条目,但子可以访问所有的父的条目;同级之间也可以随意访问。
假如有模块outer
和outer::inner
,则需要构建如下文件结构:
src/
main.rs
outer/
mod.rs
inner.rs
//main.js
mod outer; //会去找../outer.rs或者../outer/mod.rs
fn main() {
outer::outer_function();
outer::inner::inner_function();
}
//outer/mod.rs
pub mod inner; //会去找../inner.rs或者../inner/mod.rs
pub fn outer_function() {
println!("This is an outer function!");
}
//outer/inner.rs
pub fn inner_function() {
println!("This is an inner function!");
}
Path用于在模块系统中定位项(如结构体、函数、模块等)
绝对路径: use crate::module::SubModule::function;
相对路径: use module::SubModule::function;
用crate
表示根模块。
用self
表示自己。
用super
来表示父模块,功能类似于../
。
use将path导入到作用域内,但仍旧遵守私有规则,类似于cpp的using namespace。
对于函数,一般引入到其模块为止,否则直接引入到函数的话容易看不清函数归属于哪个模块;
但对于其他条目,则一般直接引入到条目本身,除非出现重名。
使用as(use xxx as yy
)可以指定别名。
use过来的东西默认为私有的,无法被上级访问到。在use前面加pub即可让use的模块也能被上级使用。
可以使用嵌套路径来导入多个前缀相同的路径:
mod outer {
pub mod inner {
pub fn function1() {
println!("This is function 1!");
}
pub fn function2() {
println!("This is function 2!");
}
}
}
// 使用嵌套路径来导入inner、function1和function2
use outer::inner::{self, function1, function2};
fn main() {
inner::function1();
function1();
function2();
}
*
是通配符,表示“所有”。
类似export,可以将某个Path条目以最少层数的Path对外暴露。
cargo new ProjectName
: 创建项目。cargo build
: 构建项目。加上--release
则以发布方式构建,运行更快。会先更新crates.io,并生成(修改)lock文件使其符合toml。cargo run
: 先编译(有必要的话)后执行。cargo check
: 检查代码保证能过编译,但不会生成可执行文件(比build快)。cargo test
: 执行项目中的所有测试。Cargo.toml
是对package或者workspace的description,称为manifast。virtual manifast则是仅仅描述一个workspace,且不包含任何一个package。
toml = Tom’s Obvious,Minimal Language
可以在[dependencies]
后面添加依赖。等号后面可省略。^
表示和所指版本API兼容的都可用。
[dependencies]
rand = "^0.3.14"
lock文件(Cargo.lock
)在build后生成,表示目前所用的确切版本。似乎默认使用最新小版本。
调用cargo update
可以对各进行更新,且只更新小版本号,如0.3.14只会被更新到0.3.23而不是0.4.0。
有多个crate时,可以在项目根目录创建一个用于聚合的toml,指定各个crate的路径,然后在对应路径中创建对应的crate(cargo new)。
[workspace]
members = [
"package1",
"package2",
]
crate之间的依赖需要在crate内的toml的dependencies通过路径指定。
Build Scripts - The Cargo Book
在跟目录下的build.rs
中的rust代码会在包构建前运行。println出来的字符串会被用于告知cargo。
如打印cargo:rustc-env=VAR=VALUE
就会建立环境变量VAR
,其值为VALUE
,可以在程序里使用std::env::var("TEST_FOO").unwrap();
访问。
如打印cargo:rustc-cfg=feature="pass"
就会使得#[cfg(feature = "pass")]
为真。
用途:
// build.rs
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("hello.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, World!\"
}
"
).unwrap();
println!("cargo:rerun-if-changed=build.rs");
}
// src/main.rs
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
fn main() {
println!("{}", message());
}
fn main() {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let your_command = format!(
"rustc-env=TEST_FOO={}",
timestamp
);
println!("cargo:{}", your_command);
let your_command = "rustc-cfg=feature=\"pass\"";
println!("cargo:{}", your_command);
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
#[cfg(test)]
的cfg是configuration,表示有指定的配置选项(test)才会执行。
使用cargo test
来执行项目中的所有测试。
每个测试都在独立的线程里面。
一个测试panic了(让线程挂掉)则表示其失败。
使用assert_eq!
和assert_ne!
断言相等性,断言失败后会用Debug的形式打印两个参数(要求参数实现Debug trait)。
断言后可以继续加多个参数,使用类似format!
的形式,作为自定义错误输出。
又是需要检测代码是否能在某个情况下成功触发panic。需要在测试函数(#[test]
)下面再加上#[should_panic]
,则panic的时候才算通过。为了防止不正常的panic也导致测试通过,可以使用#[should_panic(expected = "abc abc")]
来检查panic信息中是否包含expected参数内的字符串,只有当包含了的时候才算测试通过。
测试函数也可以改成以Result为返回值,若返回的是Err则测试失败。
使用#[ignore]
忽略测试。
上面的写在代码crate内的#[cfg(test)]
module就是单元测试。
集成测试文件要写在tests
文件夹(与src文件夹同级)内。tests
下的文件夹内的文件不会被视为集成测试文件。每个测试文件都是独立的crate。不用写mod,直接写#[test]
测试函数即可。只有cargo test
时才会编译。
cargo test --test FileName
可以运行某个集成测试文件内的所有测试。
集成测试中如果有些文件只是为了提供帮助而未含有#[test]
测试函数,则应该将其写到子文件夹里面去,防止出现无意义的空输出pass。
binary crate意味着独立运行,无法构造集成测试。
日常开发三方库精选 - Rust语言圣经(Rust Course)
日志门面 log - Rust语言圣经(Rust Course)
提供了几个宏来输出不同层级的log。
use log::{info, trace, warn};
pub fn shave_the_yak(yak: &mut Yak) {
trace!("Commencing yak shaving");
loop {
match find_a_razor() {
Ok(razor) => {
info!("Razor located: {}", razor);
yak.shave(razor);
break;
}
Err(err) => {
warn!("Unable to locate a razor: {}, retrying", err);
}
}
}
}
但是log并不会直接输出到某个地方,需要配合日志库使用,如env_logger
。
use log::{debug, error, log_enabled, info, Level};
fn main() {
// 注意,env_logger 必须尽可能早的初始化
env_logger::init();
debug!("this is a debug {}", "message");
error!("this is printed by default");
if log_enabled!(Level::Info) {
let x = 3 * 4; // expensive computation
info!("the answer was: {}", x);
}
}