Rust
该文摘抄或总结自《Rust 权威指南》将持续跟进
基础篇
Hello, world!
fn main() {println!("Hello, world!");
}
变量声明
使用let 关键字进行声明:
let 变量名: 类型;
也可以同时赋初始值:
let 变量名: 类型 = 值;
编译器可对部分值的类型进行推导:
let 变量名 = 值;
变量类型——标量类型
1.整型
i8、i16、i32,i64、i128、isize
u8、u16、u32、u64、u128、usize
isize、usize为平台相关类型
let num: i32 = 100;
debug 模式下变量溢出会panic
release 模式下变量溢出会形成环绕,即最大数+1会变成该类型最小的数。若想显示环绕则使用标准库Wrapping类型。
整数字面量 | 示例 |
---|---|
Decimal | 999_999 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1010_0101 |
Byte (u8 only) | b’Z’ |
默认推导类型:i32
2.浮点型
f32、f64
let num: f32 = 100.00;
浮点数遵循IEEE-754浮点数标准
默认推导类型:f64
3.布尔型
bool
let is_false: bool = false;
4.字符类型
char
let ch: char = 'a';
变量类型——复合类型
两种内置符合类型
1.元组类型
定义:
let tup: (i32, f32, bool) = (1, 2.0, true);
访问:
通过元组名.索引
方式访问
tup.0、tup.1、tup.2
解构:
将元组拆分为多个变量
let (a, b, c) = tup;
2.数组类型
定义:
let a = [1, 2, 3, 4, 5, 6];
let a: [i32;6] = [1, 2, 3, 4, 5, 6];
let a: [3;6];
等价于let a = [3, 3, 3, 3, 3, 3];
访问:
通过索引访问
a[0]、a[1]
变量的可变性与不可变性
使用以上方式声明的变量均不可修改,对其赋值会提示编译错误,其体现了变量的不可变性。
error[E0384]: cannot assign twice to immutable variable `a`--> src\main.rs:31:5|
30 | let (a,b,c) = tup;| -| || first assignment to `a`| help: consider making this binding mutable: `mut a`
31 | a = 100;| ^^^^^^^ cannot assign twice to immutable variable
若想声明可变的变量则在let
后加 mut
关键字(mut: mutable)。
如:let mut a: i32 = 100;
此时对a赋值a = 200;
是允许的。
变量的隐藏性
同一作用域内可以声明同名变量,下边的变量会隐藏上边的变量。即使类型不同也是可以的。
let a: i32 = 100;
let a: f32 = 100.0;
常量
定义:
const MAX_POINTS: u32 = 100_000;
访问:
可访问,不可修改。
函数
Rust 使用函数使用“蛇形命名”,即小写字母和下划线的组合方式。
无参无返回值形式
fn hello_rust() {println!("hello, rust!")
}
有参有返回值形式
fn add_one(num :i32) -> i32 {return num + 1;
}
函数内部也可以定义函数
fn main() {fn add_one(num :i32) -> i32 {return num + 1;}println!("{}",add_one(1));
}
使用表达式作为函数返回值,不需写return和结尾分号
fn main() {println!("{}",add_one(1)); // 2
}fn add_one(num :i32) -> i32 {num + 1
}
处理多返回值可使用元组
fn f() -> (i32, i64, f32 ) {(1, 2, 3.0)
}
语句
语句以英文半角下的“;”结尾。
表达式
fn main() {let x = 15;let y = {let x = 20 + 1;x + 5};println!("{}",y);
}
x + 6
称之为表达式,x =
后边的20 + 1
也是表达式(有点像数学公式) ,表达式一般用一对{}(表示代码块或作用域)括起来,注意表达式一般写在代码块的最后,其后不写分号,写分号就变成了语句。
注释
// 单行注释,可放在语句末尾,分号后/* 多行注释放在单行使用 *//** 多行注释* 多行注释* 多行注释*//// #这是文档注释
/// ## 可以写一些特殊的文本格式,如markdown
/// ```
/// fn f() -> (i32, i64, f32 ) //返回三个值
/// ```
fn f() -> (i32, i64, f32 ) {(1, 2, 3.0)
}
控制流
if else
if
let a = 3;if a > 2 {println!("a > 2");}
if else if
let a = 3;if a > 4 {println!("a > 4");}else if a > 3{println!("a > 3");}else {println!("what ?");}
作为表达式使用,表达式中的值必须类型统一。如100、200。不可以为100,“hello”。
let a = 3;let result = if a > 3 { 100 }else { 200};
循环
rust 支持3种循环loop,for,while
break 用于打破循环,即跳出当前循环
continue 结束当前层循环
loop
let mut counter = 0;loop {counter += 1;if counter == 100 {println!("循环了100次");break;}}
作为表达式使用
let mut counter = 0;let result = loop {counter += 1;if counter == 10 {break counter * 2; // 表达式的值}};println!("The result is {}", result);
while
let mut number = 3;while number != 0 {println!("{}", number);number = number - 1;}
结束循环,跳转到tag处,常用于打破多重循环
let mut number = 3;'tag: while number != 0 {if number == 1 {break 'tag;}println!("{}", number);number = number - 1;}
for
迭代数组
iter() 为迭代器
let a = [10, 20, 30, 40, 50];for element in a.iter() {println!("the value is: {}", element);}
正向遍历与反向遍历
1…4 表示范围[1,4),左闭右开,步长为1的从左往右遍历方式
(1…4).rev():反向输出[1,4)的值
for number in 1..4 {println!("{}", number);}for number in (1..4).rev() {println!("{}", number);}
所有权
Rust使用包含特定规则的所有权系统来管理内存,这套规则允许编译器在编译过程中执行检查工作,而不会产生任何的运行时开销。
所有权规则
Rust中的每一个值都有一个对应的变量作为它的所有者 。
在同一时间内,值有且仅有一个所有者。
当所有者离开自己的作用域时,它持有的值就会被释放掉。
变量作用域
Rust 用“{}”代表作用域。
变量在进入作用域后变得有效。它会保持自己的有效性直到自己离开作用域为止。
{ // 未定义变量slet s = String::from("hello!"); // 变量s生效
} // 变量s离开作用域,Rust自动调用drop函数,销毁变量,归还内存给操作系统
变量和数据交互的方式——移动(所有权转移)
标量类型,因编译时期就可以明确的知道其占用的内存大小,不会涉及堆内存分配,将其完整的放在栈空间即可。所以b = a
;复制了a 底层的数据3,两个3都在栈空间,相互独立。
let mut a = 3;let mut b = a;a += 100;println!("{}",a); // 103println!("{}",b); // 3
变量的移动(所有权转移)
以下代码设计到String的底层结构,String是一个既有栈上内存占用,又有堆上内存占用的数据结构,其发生赋值操作s2 = s1
时,会将s1的栈上内容复制一份,给s2,同时将堆上内存的指针转移给s2。s2的出现代替了s1的存在,s1在发生赋值后,便不再可用。这样设计巧妙的避开了同一个内存区域被两次drop。
这其实就是一种浅拷贝操作,但会弃用被拷贝的变量。Rust称上述动作为移动(move
)。Rust永远不会自动地创建数据的深度拷贝。因此在Rust中,任何自动的赋值操作都可以被视为高效的。
let s1 = String::from("hello");let s2 = s1;println!("{}",s1);println!("{}",s2);
error[E0382]: borrow of moved value: `s1`--> src\main.rs:31:19|
28 | let s1 = String::from("hello");| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
29 | let s2 = s1;| -- value moved here
30 |
31 | println!("{}",s1);| ^^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
变量和数据交互的方式——克隆
深度拷贝,调用其clone()方法
let s1 = String::from("hello");let s2 = s1.clone();println!("s1 = {}, s2 = {}", s1, s2);
栈上数据赋值——实现Copy trait
trait 类似于其他语言的接口,描述了一系列行为。
Copy trait 负责栈上数据的拷贝工作,实现该trait即可将值复制给其他变量,原变量仍可用。
Drop trait 负责堆内存的释放工作。
同一种类型对于 Copy 和 Drop 只能实现其一。
内置的标量类型整型、浮点型、布尔型、字符型数据都实现了Copy trait。若元组中的所有数据类型都实现了Copy trait,那么该元组也具有被Copy的能力。
完整的存储在栈上的类型都可以实现Copy,只要有堆内存和引用其他资源的就不可以实现Copy,否则编译时报错。
用Copy 或 clone的方式对只保留在栈上的类型的数据进行复制都是可以的。
所有权与函数,参数、返回值、作用域
当函数的参数类型实现了Copy,那么传给函数参数的变量,在调用函数后,仍可使用。反之,则发生了所有权转移,参数的作用域即函数的作用域。在函数执行结束后,会自动调用drop,实现回收。所以,在函数调用处之后,是不可使用因调用函数而发生了所有权转移的变量的。若想继续使用该变量,可将其作为函数的返回值,返回后重新使用。引用可以解决这个壁垒。
引用
Rust 使用 & 代表引用语义,使用 * 代表解引用语义。
引用不持有所有权。
不可变引用——借用
let s1 = String::from("hello");let s2 = &s1;println!("{}",s1);println!("{}",s2);
上述代码s2持有了s1的引用。
引用类型作为函数的参数
fn length(text: &String) -> usize {text.len()
}
调用上述length函数,不会发生所有权转移,在函数退出时,不销毁传入的参数text。
不可变的引用也称之为借用(borrow)。对length函数中的text
参数进行修改是不被允许的。
可变引用
作为函数参数,表示为s: &mut String
fn change_string(s: &mut String) {s.push_str("_修改后的.");
}
let mut s1 = String::from("hello");change_string(&mut s1);println!("{}",s1);
以下代码比较难以理解。在使用可变引用时比较迷惑人。
同一时刻,一个变量只能有一个可变引
如下操作不被允许
let mut s1 = String::from("hello");let mut s2 = &mut s1; //------s1.push_str("_1"); // | 可变引用s2,被操作时,不能交叉操作s1s2.push_str("_2"); //--------
error[E0499]: cannot borrow `s1` as mutable more than once at a time--> src\main.rs:38:5|
37 | let mut s2 = &mut s1;| ------- first mutable borrow occurs here
38 | s1.push_str("_1");| ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
39 | s2.push_str("_2");| ----------------- first borrow later used here
如下操作同样不被允许
let mut s1 = String::from("hello");let mut s2 = &mut s1;let mut s3 = &mut s1;s2.push_str("hh");
error[E0499]: cannot borrow `s1` as mutable more than once at a time--> src\main.rs:38:18|
37 | let mut s2 = &mut s1;| ------- first mutable borrow occurs here
38 | let mut s3 = &mut s1;| ^^^^^^^ second mutable borrow occurs here
39 | s2.push_str("hh");| ----------------- first borrow later used here
如下操作被允许
let mut s1 = String::from("hello");let mut s2 = &mut s1;s2.push_str("_2");s1.push_str("_1");
使用作用域隔离可变引用
let mut s1 = String::from("hello");{let mut s2 = &mut s1;s2.push_str("hh");}let mut s3 = &mut s1;println!("{}",s3);println!("{}",s1);
发生数据竞争的条件:
1.两个或两个以上的指针同时访问同一空间。
2.其中至少有一个指针会向空间中写入数据。
3.没有同步数据访问的机制。
操作可变引用后,不可以操作原来的不可变引用
允许的
let mut s1 = String::from("hello");let r1 = &s1;let r2 = &s1;println!("{}-{}",r1,r2);let r3 = &mut s1;println!("{}",r3);
不允许的
let mut s1 = String::from("hello");let r1 = &s1;let r2 = &s1;println!("{}-{}",r1,r2);let r3 = &mut s1;println!("{}",r3);println!("{}",r1);
error[E0502]: cannot borrow `s1` as mutable because it is also borrowed as immutable--> src\main.rs:40:14|
37 | let r1 = &s1;| --- immutable borrow occurs here
...
40 | let r3 = &mut s1;| ^^^^^^^ mutable borrow occurs here
41 | println!("{}",r3);
42 | println!("{}",r1);| -- immutable borrow later used here
悬垂引用
未引入生命周期,悬垂引用不被允许
fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {let s = String::from("hello");&s
}
error[E0106]: missing lifetime specifier--> src\main.rs:39:16|
39 | fn dangle() -> &String {| ^ expected named lifetime parameter|= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime|
39 | fn dangle() -> &'static String {| +++++++
引用规则
在任何一段给定的时间里,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用,引用总是有效的。
切片
切片是一种特殊的引用形式。其不持有所有权。
切片允许我们引用集合中某一段连续的元素序列。
将整个集合转换成切片
let slice = 集合[..];
将集合一部分转换成切片
let slice = 集合[1..];// 从索引1到集合最后
let slice = 集合[1..2];// 从索引1到索引2
let slice = 集合[..2];// 从索引0到索引2
let slice = 集合[0..3];// 从索引0到索引3
切片中[start…end],start与end构成的是一个左闭右开的区间[start,end)
字符串切片
let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];println!("{}",hello);println!("{}",world);
对字符串切片时需考虑字符边界问题,UTF-8编码使用1-4字节表示一个字符。
字符串字面量就是切片。
let s = "hello, world";let s :&str = "hello world";
str类型即字符串切片类型。
结构体
定义方式
struct 结构体名 {字段名0:类型,字段名1:类型,
}
访问方式
对象.字段名
定义User结构体
struct User {name: String,age: i32,
}
使用User结构体
let user= User{name: "ophelia".to_string(),age:13,};println!("{:?}",user.name);println!("{:?}",user.age);
用函数方式创建User对象
fn new_user(name: String, age: i32) -> User {User{name:name,age: age,}
}
若函数形参名与结构体中字段重合,则不用写结构体的字段名,直接写入形参
fn new_user_two(name: String, age: i32) -> User {User{name,age,}
}
let mut user = new_user(String::from("张三"),20);user.age = 22;println!("{}",user.age);let user = new_user_two(String::from("李四"),20);println!("{}",user.name);
使用已有的结构体中的字段
方式1
let user = new_user_two(String::from("李四"),20);//使用其他user中的字段创建userlet user1 = User{name:String::from("judy"),age:user.age,};
方式2 …运算符。语法糖。
let user = new_user_two(String::from("李四"),20);//使用其他user中的字段创建userlet user1 = User{name:String::from("julia"),..user};
元组结构体
定义方式
struct 结构体名(类型1,类型2,类型3);
访问方式
对象.索引
定义元组结构体
struct Color(i32, i32, i32);struct Point(i32, i32, i32);let black = Color(0, 0, 0);let origin = Point(0, 0, 0);println!("{}",black.0);
空结构体
定义方式
struct 结构体名 {}
空结构体一般用作实现trait。实现trait时,不需要使用结构体中的数据。
空结构体与空元组类似 let tup = ();
结构体数据的所有权
若结构体中所有字段都是非引用类型,那么只要结构体携带的数据时有效的,那么结构体就是有效的若结构体中有字段为引用类型,则需要引入生命周期的概念。
为结构体添加派生宏,Debug,实现打印功能
不在结构体上添加 #[derive(Debug)]
是无法实现打印功能的。
还需要修改打印宏的占位符的参数 println!("{:?}",user);
加上:?
。
#[derive(Debug)]
struct User {name: String,age: i32,
}let user= User{name: "ophelia".to_string(),age:13,
};println!("{:?}",user); // User { name: "ophelia", age: 13 }
方法
方法与函数类似:都使用fn关键字及一个名称来进行声明;它们都可以拥有参数和返回值;另外,它们都包含了一段在调用时执行的代码。但是,方法与函数依然是两个不同的概念,因为方法总是被定义在某个结构体(或者枚举类型、trait对象)的上下文中,并且它们的第一个参数永远都是self,用于指代调用该方法的结构体实例。
定义方式
impl 结构体名 {fn 方法名0(self,参数1:类型) -> 返回值类型 {返回值}fn 方法名1(&self,参数1:类型,参数2:类型) -> 返回值类型 {返回值}fn 方法名2(&mut self,参数1:类型) -> 返回值类型 {返回值}fn 函数名(参数1:类型)-> 返回值类型 {返回值}
}
代码
#[derive(Debug)]
struct User {name: String,age: i32,
}impl User {fn say(&self) {println!("say");}fn println(&mut self) {self.name = String::from("张三");println!("{:?}",self);}fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器User{name,age,}}
}fn main() {let mut user = User{name:String::from("judy"),age: 16};let mut user = User::new_user(String::from("judy"),15);user.say();user.println();
}
关联函数
为结构体绑定函数,但是函数参数不传入self。则称之为关联函数,关联函数常用于创建对象。
访问方式一般为: let 变量名 = 结构体::关联函数名(参数);
impl User {fn say(&self) {println!("say");}fn println(&mut self) {self.name = String::from("张三");println!("{:?}",self);}fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器User{name,age,}}
}
多impl块
Rust 支持将实现同一类型的方法,拆分到多个impl块中
impl User {fn get_name(self) ->String {self.name}
}
impl User {fn get_age(self) ->i32 {self.age}
}