Rust_Chapter_1_变量
这一章节内容主要是Rust的变量
前言
从现在开始,我正式开始学习Rust。Rust相对于C++在出于安全的规范优势不必多说,我发现Rust也能用于嵌入式开发。
因而我受够了,快用Rust重写(乐)
在写这一章的时候,我手头看的这一本书是《Rust权威指南》,虽说序论中说并不适合新手,但是就我读了前三章似乎也没有遇见特别难懂的概念。这一章包括之后,都是这本书的学习笔记
Hello Rust
新学一门编程语言,肯定少不了Hello_World,基本礼仪1
2
3fn main(){
println!("Hello Rust!");
}
简简单单的一行,与C++类似fn main()
是main函数的声明,入口函数就是main()
函数,没什么好说的println!()
是一个宏而非函数,这点在后面在解释println!()
可以使用类似C++20std::format
的语法形式,具体如下1
2println!("{1} learn rust {0}",666,"feipiao");
//输出结果 feipiao learn rust 666
变量
1 | fn main(){ |
首先,Rust中的变量默认是不可变变量,也就是let x = 666;
,这和C++有很大的不同,我想不出C++有什么平替,因为它并不是C++中加const
的那种常量,C/C++中的变量其实都是第二条let mut y = 233;
,为可变变量
还记得C/C++有一个mutable
关键字么,这里的mut
其实就是它的缩写,那么他俩有什么区别呢?
不可变变量
不可变变量保证了数据不会被修改,例如运行如下的错误代码会报错1
2
3
4fn main(){
let x = 666;
x = 100;//这是会报错滴
}
而以下代码就不会报错,因为这里666
是利用let
被隐藏/shadow了,我们可以重复使用let
关键字并配以相同的名称来不断地隐藏变量1
2
3
4fn main(){
let x = 666;
let x = 100;//666被隐藏了,x代表100,这保证了666这个数据没有被修改
}
值得一提的是,这种shadow甚至可以改变x的类型,也就是说除了x的名字没变,它这个名字代表的本身是什么完全可以变掉
也就是说,把一个人的名字给了另一个人,这个人也就自然被隐藏起来了,但谁也还是自己,没被修改不是么
个人感觉这和C/C++作用域下名称的覆盖类似,也许那也是一种shadow呢?
可变变量
像可变变量就很类似C/C++了,其在第一次声明的时候类型就已经被确定了1
2
3
4fn main(){
let mut x = 666;
x = 233;
}
这里声明了x是一个可变变量,第二句就修改了它的值,但x的类型在第一句的时候就已经被确认是一个整型了(下面会讲到,这里是自动类型推导),如果在这个时候把第二句改成x = 233.2
就会报错
let即隐藏&可变与不可变的底层内涵
1 | fn main() { |
注意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
6fn 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 |
最后一行的isize
和usize
的长度取决于程序运行的目标平台,在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
浮点类型
基本的两个类型f32
与f64
,依照遵循IEEE-754,Rust中浮点数字面量默认推导为f64
Bool类型
没啥好说的,和其它编程语言一致,都是True
和False
,类型名就是Bool
字符类型
即char
类型,注意字符类型是用单引号包围的,而字符串是用双引号包围的
Rust中的char
类型占4个字节,是一个Unicode标量值,头皮发麻了
元组类型
元组类型可以将多个不同类型的多个值组合进一个复合类型中1
2
3
4
5fn 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
6fn main(){
let x = (500,6.4,1);
let a = x.2;
let b = x.0;
let c = x.1;
}
数组类型
与元组类型不同,数组中的每个值只能够是相同的类型,相同的是类型在一开始必须就能推导或者显式声明,而且数组的大小也必须在一开始就确定1
2
3
4
5
6fn 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++留下引发一系列的安全问题的隐患