前言
耗时5分钟
目录
一、变量
定义变量
如何声明变量?
let a = 123;
Rust 它是强类型语言,具备自动判断变量类型能力。上面的 a 自动会推导成 i32类型(数据类型章节会介绍)。
等价于
let a:i32 = 123;
再来看下下面语句是否正确?
let a = 123;
a = 456;
上述代码在IDE中就会提示cannot assign twice to immutable variable 'a' , 所以let 只是定义不可变变量字段关键字
怎么定义可变变量?
引入 mut 关键字
let mut b = 123;
b = 456;
不可变变量和常量有什么区别?
看下面例子就知道了
fn main() {
let a = 23;
let a = 45;
println!("a = {}", a);
}
输出结果是 a = 45;
但是按照下面这样写就不行了
fn main() {
const a:i32 = 23;
let a = 45; // 这行报错,introduce a variable instead: `a_var`
println!("a = {}", a);
}
所以变量的值可以重新绑定,一个变量可以多次使用,允许同名变量存在,后面使用的将是最后一个同名变量,这种机制在 rust 中被称为 Shadowing 机制,隐藏机制,会将之前的的同名变量隐藏。
常量有命名规范,一般使用全大写字母加下划线。例如 const MAX_LENGTH:u32 = 10_000;(简写,就是=10000)
二、数据类型
你是不是对之前提到的 i32 很疑惑?怎么没见过,不应该是int 、boolean 、bool 这种吗?
这是Rust的基本类型,Rust有整数型、浮点型、布尔型、字符型、复合型五种基本类型。
整数型
分为无符号和有符号两种
| 位长度 | 有符号 | 无符号 |
|---|---|---|
| 8bit | i8 | u8 |
| 16bit | i16 | u16 |
| 32bit | i32 | u32 |
| 64bit | i64 | u64 |
| 128bit | i128 | u128 |
| arch | isize | usize |
有符号的就是 i开头,无符号就是u开头
isize 、usize :如果计算机是64位的,那就是64位,在32位机器上就是32位的。通常用于表示容器的大小或者数据的大小,可用于对某种集合进行索引操作。
一些表示方法示例
- 十进制 98_222
- 十六进制 0xff
- 八进制 O0 77
- 二级制 0b1111_0000
- Byte(u8 only) b'A'
浮点型
| 32位 | 64位 |
|---|---|
| f32 | f64 |
let x = 2.0; //默认等价于 let x:f64 = 2.0;
布尔型
let x = true;//等价于 let x:bool = true;
字符型
字符型,4个字节,单引号,使用char关键字
let a = '❤'; //等价于 let a:char = '❤';
它代表 Unicode标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至表情符号和零宽度空格,所以上面这样写每问题。
复合型
let (x,y,z) = (1,2,3);
let tup:(i32, f32, f64) = (10, 10.1, 10.2);
let (i,j,k) = tup;
相当于模式匹配,这种利用元组的写法来达到赋值,内部对应的数据结构可以不一样。而数组就不同了。
数组就必须要一样的类型
let a = [1,2,3,4,5]; //等价于 let a:[i32;5] = [1,2,3,4];
let b = [3;5] ; //等价于 let b = [3, 3, 3, 3, 3, 3 ];
同样数组也符合可变和不可变准则,想要可以修改数组的值,需要加上mut关键字,例如 let mut a = [1, 2,3];
三、基本操作
条件语句
fn main() {
let a = 1;
let b ;
if (a == 1) {
b = 1;
}else if (a == 2){
b = 2;
}else{
b = 3;
}
println!("b == {}", b);
}
循环
fn main() {
let mut number = 1;
while number != 4 {
println!("{}", number);
number += 1;
}
println!("EXIT");
let a = [10, 20, 30, 40, 50];
for i in a.iter() {
println!("值为 : {}", i);
}
for i in 0..5 {
println!("a[{}] = {}", i, a[i]);
}
}
除了上面三种循环外,还有loop循环(无限循环),不推荐使用
fn main() {
let s = ['R', 'U', 'N', 'O', 'O', 'B'];
let mut i = 0;
let location = loop {
let ch = s[i];
if ch == 'O' {
break i;
}
i += 1;
};
println!(" \'O\' 的索引为 {}", location);
}
四、函数
函数定义
函数示例如下
fn main() {
print_function();
print_function2(3);
}
fn print_function(){
let x = 3;
println!("x = {}", x);
}
fn print_function2(x: i32){
println!("x = {}", x);
}
print_function()函数的定义无所谓,不像C++,必须要先声明
函数体的语句和表达式
fn main() {
let a = 6;
let b = test();
let c = test;
print!("c() == {}" , c());
}
输出:
c() == 3
还有比较花的写法
fn main() {
let x = 5;
let y = {
let x = 3;//它的生命周期只在{}中
println!("x == {}" , x);
x + 1
};
println!("x == {}",x);
println!("y == {}" , y);
}
输出
x == 3
x == 5
y == 4
五、所有权
所有权是Rust最独特的特性,它让Rust无需GC就可以保证内存安全。它这个规则,在编译器编译的时候就会检查。
Rust它自己内存申请和释放,也是基于堆和栈,对于一些定义固定大小,例如定义的一个变量,这些处于内存的栈中。而对于无法在编译阶段令程序分配固定长度的内存空间,都会在堆中去分配。基本上所有说的内存资源都是堆空间。在内存释放的时候,Rust它自己会调用drop函数。
即拥有的某个变量走出作用域时,内存就会立即自动free掉,交还给系统
那么Rust是怎么申请内存的?
- 操作系统会在heap中找到一块足够大的空间,把它标记为在用,并返回一个指针(这个空间的地址)。
- 依旧是通过指针访问heap中的数据
所有权解决的问题
- 跟踪代码哪些部分正在使用heap的哪些数据
- 最小化heap上的重复数据
- 清理heap上未使用的数据以避免空间不足
所有权的三条规则
- 每个值都有一个变量,这个变量是该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域时,该值将被删除
简而言之,就是作用域内,一个值只能有一个所有者
数据的移动
1.栈
基本类型都是放在栈中,这种属于直接复制数据给另一个变量
let x = 4;
let y = x;
能够在栈上存放的数据,都是实现了Copy trait的,什么是Copy? 理解为栈上复制的接口,有这个就可以复制栈上的数据,所以遇到不支持栈上复制的数据,就可以实现Copy trait。基本标量类型都是支持的。
例如下y 就是复制了 x的栈上数据,复制后 , x 和 y都能用
2.堆
let s1 = String::from("hello");
let s2 = s1;
println!("s1 = {}", s1);//报错
这个和Java类似,s1 存在栈中,指向堆内存。s2 也是存在于栈中,指向同一块堆内存,s1 和 s2 不相同
但是你知道 第二句 let s2 = s1 发生了什么吗?
- 将栈中数据赋值给
s2,此时s1已经无效了,不能再用了,等出了作用域就都被清理了。这可以叫做所有权移交 - 这完美解决了
C++中,一个对象内存被释放两次的问题
既然是堆,那肯定要释放资源,它是自动调用drop函数释放,实现了Drop trait的才会在堆上申请内存,内存释放。 它和Copy trait 一个类型中只能存在一个。
数据的拷贝
我们应该都知道深拷贝和浅拷贝,理念都是一样的。
刚才介绍的堆的例子,其实就是浅拷贝,知识拷贝了栈上数据而已,指向的堆内存还是一样的。我们称为这个叫Move,而深拷贝称为Clone
Rust有一个设计原则,不会自动创建数据的深拷贝,所以运行时性能很好。想要深拷贝,就需要调用clone方法。
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}", s1);
}
六、所有权在函数上的应用
实现了Copy Trait类型的所有权示例
基本类型都是实现了Copy Trait,就是之前说的栈上复制内存
fn main() {
let x = 8;
make_copy(x); // 传入的是 x的副本
println!("x == {}", x); //这块依旧可以操作x
}
fn make_copy(number: i32){
println!("number = {} ", number);
}
输出
number = 8
x == 8
实现了Drop Trait类型的所有权示例
fn main() {
let s = String::from("Hello World");
make_drop_test(s);
println!("s == {}", s); //这行直接编译器前就语法报错
}
fn make_drop_test(str: String){
println!("str == {}", str);
}//str被释放
强制运行结果是
10 | let s = String::from("Hello World");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
11 | make_drop_test(s);
| - value moved here
12 | println!("s == {}", s);
| ^ value borrowed here after move
说没有实现Copy Trait, 也就是说make_drop_test(s) 传递的不是副本,在调用 make_drop_test()后,str也就出了作用域,立马就被释放了。
返回值的所有权移动
一个变量的所有权移动符合以下规则
- 把一个值赋给其它变量就会发生移动
- 当一个包含heap数据的变量离开了作用域,它的值就会被drop函数清理。除非数据所有权移动到了另一个变量
例如
fn main() {
let s = String::from("Hello World");
let s2 = make_drop_test(s); //所有权给了s2
println!("{}", s);//报错 s的所有权移交给了s2 ,s不能再用了
println!("{}", s2);
}
fn make_drop_test(str: String) -> String{
str
}
但是我们其实想调用make_drop_test()只是使用s的值,我不想让它的所有权移交出去,怎么办呢?
这就需要用到引用了
七、引用与借用
引用与借用
同样是之前的所有权移交例子,修改一下
fn main() {
let s = String::from("Hello World");
let s2 = calc_length(&s); //传递引用
println!("{} len is {}",s, s2);//我们还能继续用s
}
fn calc_length(str: &String) -> usize{
str.len()
}
输出
Hello World len is 11
使用引用,就能实现我们引用某些值而不取得它的所有权。
我们这种把引用作为函数参数的行为称为借用,我把引用借给你用,所有权还归我,但是你只能读,不能写。
例如
fn main() {
let s = String::from("Hello World");
let s2 = calc_length(&s); //传递引用
println!("{} len is {}",s, s2);
}
fn calc_length(str: &String) -> usize{
str.push_str("Hello World"); //这行会报错
str.len()
}
会报错
cannot borrow `*str` as mutable, as it is behind a `&` reference
`str` is a `&` reference, so the data it refers to cannot be borrowed as mutablerustc[E0596](https://doc.rust-lang.org/error-index.html#E0596)
就是说我给你了,但是我没有给你写的权限,除非加一个mut关键字,成为可变引用。
fn main() {
let mut s = String::from("Hello World");
let s2 = calc_length(&mut s); //传递引用
println!("{} len is {}",s, s2);
}
fn calc_length(str: &mut String) -> usize{
str.push_str("Hello World");
str.len()
}
引用注意事项
在一个作用域内只能有一个可变引用。
- 这样就避免了多个指针访问,同时对同一个数据进行写入
1.不在同一个作用域内的可变引用
fn main() {
let mut s = String::from("Hello Rust!");
{
let s1 = &mut s;
} //可变引用 s1出了作用域
let s2 = &mut s;
}
2.同一个作用域内的可变引用
fn main() {
let mut s = String::from("Hello Rust!");
{
let s1 = &mut s;
let s2 = &mut s; //这里会报错,只要你用了就会报错
println!("s1 = {} , s2 = {}", s1,s2);
}
}
3.同一时刻不能有一个可变引用和一个不可变引用
可变引用可以改变值,不可变引用
fn main() {
let mut s = String::from("Hello Rust!");
let m = &s;
let s1 = &mut s;//报错
println!("{} {}", m, s1);
}
`mut` can be used in several situations. The first is mutable variables, which can be used anywhere you can bind a value to a variable name
悬空引用
什么叫悬空引用?
- 一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其它人用了。
- Rust里,编译器可以保证引用永远都保证不是悬空引用 例如
fn test() -> &String{ //报错
let s = String::from("Hello");
&s
}//s出了作用域 但是s竟然作为返回值,对s的引用返回了。s又被释放了,所以返回的引用指针会指向一个已经被释放的地址