Rust_Chapter_1_变量

Rust_Chapter_1_变量

这一章节内容主要是Rust的变量

前言

从现在开始,我正式开始学习Rust。Rust相对于C++在出于安全的规范优势不必多说,我发现Rust也能用于嵌入式开发。
因而我受够了,快用Rust重写(乐)

在写这一章的时候,我手头看的这一本书是《Rust权威指南》,虽说序论中说并不适合新手,但是就我读了前三章似乎也没有遇见特别难懂的概念。这一章包括之后,都是这本书的学习笔记

Hello Rust

新学一门编程语言,肯定少不了Hello_World,基本礼仪

1
2
3
fn main(){
println!("Hello Rust!");
}

简简单单的一行,与C++类似
fn main()是main函数的声明,入口函数就是main()函数,没什么好说的
println!()是一个宏而非函数,这点在后面在解释
println!()可以使用类似C++20std::format的语法形式,具体如下
1
2
println!("{1} learn rust {0}",666,"feipiao");
//输出结果 feipiao learn rust 666

变量

1
2
3
4
fn main(){
let x = 666;
let mut y = 233;
}

首先,Rust中的变量默认是不可变变量,也就是let x = 666;,这和C++有很大的不同,我想不出C++有什么平替,因为它并不是C++中加const的那种常量,C/C++中的变量其实都是第二条let mut y = 233;,为可变变量
还记得C/C++有一个mutable关键字么,这里的mut其实就是它的缩写,那么他俩有什么区别呢?

不可变变量

不可变变量保证了数据不会被修改,例如运行如下的错误代码会报错

1
2
3
4
fn main(){
let x = 666;
x = 100;//这是会报错滴
}

而以下代码就不会报错,因为这里666是利用let隐藏/shadow了,我们可以重复使用let关键字并配以相同的名称来不断地隐藏变量
1
2
3
4
fn main(){
let x = 666;
let x = 100;//666被隐藏了,x代表100,这保证了666这个数据没有被修改
}

值得一提的是,这种shadow甚至可以改变x的类型,也就是说除了x的名字没变,它这个名字代表的本身是什么完全可以变掉
也就是说,把一个人的名字给了另一个人,这个人也就自然被隐藏起来了,但谁也还是自己,没被修改不是么
个人感觉这和C/C++作用域下名称的覆盖类似,也许那也是一种shadow呢?

可变变量

像可变变量就很类似C/C++了,其在第一次声明的时候类型就已经被确定了

1
2
3
4
fn main(){
let mut x = 666;
x = 233;
}

这里声明了x是一个可变变量,第二句就修改了它的值,但x的类型在第一句的时候就已经被确认是一个整型了(下面会讲到,这里是自动类型推导),如果在这个时候把第二句改成x = 233.2就会报错

let即隐藏&可变与不可变的底层内涵

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let a = 233;
println!("{}", a);
let mut a = 233;
println!("{}", a);
a = 666;
println!("{}", a);
}
//正确编译,结果如下
//233
//233
//666

注意let代表的就是shadow的含义,既然是隐藏,那么意味着类型可以被更改,也就是说第二个let mut a = 233;时a就变成了一个可变变量了,但原来上面第一个的let a = 233中233还存在于栈上,并没有被改变,只是a不再能访问到它了而已。第三句由于a已经是一个可变变量了,自然也是成立的

可以看看如下缩减的汇编码,本篇文档记录时使用的环境为rustc 1.69.0 (84c898d65 2023-04-16)
上述代码经过rustc -O --emit asm=main.s .\main.rs生成的完整的汇编文件github仓库链接

1
2
3
4
5
6
7
8
9
10
11
12
13
_ZN4main4main17hd0d97daef3a32cf2E:
...
movl $233, 36(%rsp)
...
callq _ZN3std2io5stdio6_print17hce7a376ab49946d5E
...
movl $233, 32(%rsp)
...
callq _ZN3std2io5stdio6_print17hce7a376ab49946d5E、
...
movl $666, 32(%rsp)
...
callq _ZN3std2io5stdio6_print17hce7a376ab49946d5E

可以清晰的看到Rust所承诺的不可变变量的不可被修改性,也可以看到可变变量在原位被更改,而let就是生成一个崭新的变量,在地址上区别于原对象,这不像C++,因为C++是不允许局部静态变量重复被初始化的

常量

const而不是let来声明一个常量,常量和变量是不同的,不论变量可变还是不可变,常量不可以使用mut修饰,常量不仅默认不可变,还总是不可变,这体现为下列错误

1
2
3
4
5
6
fn main() {
const MAX: u32 = 100;
println!("{}", MAX);
const MAX: u32 = 10;//ERROR!!!报错啦
println!("{}", MAX);
}

即不允许MAX被重定义
常量在声明时候必须第一时间显式标注类型
常量只能绑定到常量表达式上,不能被绑定到函数的返回值上,或其它需要在运行时计算的值

变量的类型

Rust是一个强类型的语言,但从上面的例子好像看不出来,因为它存在有自动的类型推导,当然我们也可以显式的给它声明变量是什么类型,如下

1
let mut test: u32 = 42;

有时候Rust无法推导出变量的类型,我们就不能去掉这个显式声明的过程,如下段代码,就不能去除: u32,否则就会愉快的报错
1
let guess: u32 = "42".parse().expect("Not a number!");

Rust中内建了4种基础的标量类型:整型、浮点数、布尔值与字符,2种内置的基础的复合类型:元组(tuple)和数组(array)

整型

没啥好说的,直接上表格

Rust中的整数类型

长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
arch isize usize

最后一行的isizeusize的长度取决于程序运行的目标平台,在64位上就64位,32位上就32位

你可以使用如下表的方式书写整型字面量,注意,除了Byte,其余所有的字面量都可以使用整型后缀,比如57u8,代表一个使用了u8类型的整数57。同时你可以使用_作为分隔符方便读数,比如1_000

Rust中的整型字面量

整型字面量 示例
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte(u8 only) b’A’

关于整型的类型溢出,在debug版本下会直接被Rust检测到,弹出panic并结束程序,在release版本下会形成经典的C/C++中的overflowing,这本书中称为二进制补码环绕,如果希望显示使用环绕,可以使用标准库中的类型Wrapping

浮点类型

基本的两个类型f32f64,依照遵循IEEE-754,Rust中浮点数字面量默认推导为f64

Bool类型

没啥好说的,和其它编程语言一致,都是TrueFalse,类型名就是Bool

字符类型

char类型,注意字符类型是用单引号包围的,而字符串是用双引号包围的
Rust中的char类型占4个字节,是一个Unicode标量值,头皮发麻了

元组类型

元组类型可以将多个不同类型的多个值组合进一个复合类型中

1
2
3
4
5
fn main(){
let tup:(i32,f64,u8) = (500,6.4,1);

let (x,y,z) = tup;
}

这里第一行创建了一个元组,注意这里的类型显示说明是不必要的,我们也可以写成let tup = (500,6.4,1);

第二行看起来像是结构化绑定,在这里被称为是解构

除此以外我们还可以使用索引并通过点号来访问元组中的值

1
2
3
4
5
6
fn main(){
let x = (500,6.4,1);
let a = x.2;
let b = x.0;
let c = x.1;
}

数组类型

与元组类型不同,数组中的每个值只能够是相同的类型,相同的是类型在一开始必须就能推导或者显式声明,而且数组的大小也必须在一开始就确定

1
2
3
4
5
6
fn main(){
let a:[i32;5] = [1,2,3,4,5];
//显式说明类型以及个数
let a = [1,2,3,4,5];
//隐式推导
}

特殊用法,如果想创建一个含有相同值的数组,可以如下简写

1
let a = [3;5];//注意中括号里面是分号

访问数组的元素与C/C++一致,即arrayname[index],如果数组越界访问,也会引起运行时的panic,程序会崩溃退出,十分的保守与安全,不像C/C++留下引发一系列的安全问题的隐患