变量和可变性
变量只是默认不可变
变量只是默认不可变,你可以在变量名之前加 mut 来使其可变。除了允许改变值之外,mut 向读者表明了其他代码将会改变这个变量值的意图。
c++在很多时候调试都是在看变量在哪里被超预期的被修改,不可变一定程度上可以规避这种问题,但不可变并不一定完全合理的,当确实需要修改时不可能全部通过通过拷贝语义重新复制1一个可变对象。如rust所说适当地使用可变变量,可能比复制和返回新分配的实例更快。对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解,为可读性而牺牲性能或许是值得的。换句话说,可变或不可变视变量使用情况定,由用户来决策,rust只是语法上给出一种更加安全的方案,让编译器来尽可能的在编译期就暴露潜在的错误,这也是rust语言的核心。
变量和常量的区别
const MAX_POINTS: u32 = 100_000;
常量(constants),类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
- 声明常量使用
const关键字而不是let,并且 必须 注明值的类型。 - 常量可以在任何作用域中声明,包括全局作用域
- 常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。
- 在声明它的作用域之中,常量在整个程序生命周期中都有效,这点又有点像C++的静态变量
隐藏(Shadowing)
可以定义一个与之前变量同名的新变量,而新变量会 隐藏 之前的变量。重复使用 let 关键字来多次隐藏。
隐藏与将变量标记为 mut 是有区别的,
- 隐藏必须要使用
let关键字 - 当再次使用
let时,实际上创建了一个新变量,我们可以改变值的类型
// 隐藏
let spaces = " ";
let spaces = spaces.len();
// 可变,但不可改变类型,会报错
let mut spaces = " ";
spaces = spaces.len();
数据类型
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,必须增加类型注解。这点和C++特性 auto或者模板参数隐式推断很像,能推断就推,不能推断有歧义就报错。
标量类型
Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。
整型
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 |
u8 |
| 16-bit | i16 |
u16 |
| 32-bit | i32 |
u32 |
| 64-bit | i64 |
u64 |
| 128-bit | i128 |
u128 |
| arch | isize |
usize |
和C++基本一样,取值范围相同,多了个arch由编译平台32还是64位决定,类似于c++的long类型。同样有符合和无符号数,同时有溢出,允许使用 _ 做为分隔符以方便读数,例如1_000。
| 数字字面值 | 例子 |
|---|---|
| Decimal (十进制) | 98_222 |
| Hex (十六进制) | 0xff |
| Octal (八进制) | 0o77 |
| Binary (二进制) | 0b1111_0000 |
Byte (单字节字符)(仅限于u8) |
b'A' |
比如u8 ,它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 检查这类问题并使程序 panic,release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装。
依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping。
即debug模式下溢出会报错,release模式下会补码包装,如果人为需要这种溢出,显式使用Wrapping。
浮点型
f32 是单精度浮点数,f64 是双精度浮点数。
let x = 2.0; // f64,即字面值是f64
let y: f32 = 3.0; // f32,指定绑定类型
布尔型
Rust 中的布尔类型有两个可能的值:true 和 false
字符类型
Rust 的 char 类型是语言中最原生的字母类型。
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
Rust 的 char 类型的大小为四个字节(four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。
拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值
复合类型
复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)
元组类型
-
使用包含在圆括号中的逗号分隔的值列表来创建一个元组
-
元组长度固定:一旦声明,其长度不会增大或缩小。
-
元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的
let tup: (i32, f64, u8) = (500, 6.4, 1); // 元组定义
let tup = (500, 6.4, 1); // 隐式推断类型
let (x, y, z) = tup; // 使用模式匹配(pattern matching)来解构(destructure)元组值
println!("The value of y is: {}", y);
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0; // 也可以使用点号(.)后跟值的索引来直接访问它们
数组类型
与元组不同,数组中的每个元素的类型必须相同,数组是固定长度
let a = [1, 2, 3, 4, 5];
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量
let a = [3; 5]; // 创建包含相同值的数组,可以指定初始值,后跟分号
vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1]; // 可以使用索引来访问数组的元素
let a = [1, 2, 3, 4, 5];
let index = 10;
let element = a[index];
println!("The value of element is: {}", element);
编译并没有产生任何错误,不过程序会出现一个 运行时(runtime)错误并且不会成功退出。
为了确认如果不使用这个变量是否会报错。
use rand::Rng;
fn main() {
let a = [1, 2, 3, 4, 5];
let index = rand::thread_rng().gen_range(6, 101);
println!("rand = {}", index);
a[index];
}
#[warn(unused_variables)]
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 37', src\main.rs:7:19
可见编译时报错和运行时panic都没有缺席。
函数
Rust 不关心函数定义于何处,只要定义了就行
在函数签名中,必须 声明每个参数的类型。这和C++一致
let x = (let y = 6); // 错误
let y = 6 和c++并不一样,语句并不返回值
包含语句和表达式的函数体
Rust 是一门基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别
语句(Statements)是执行一些操作但不返回值的指令
表达式(Expressions)计算并产生一个值
函数体由一系列的语句和一个可选的结尾表达式构成。
表达式作为语句的一部分
let x = (let y = 6); //语句不返回值,这里报错
函数定义也是语句
函数调用是一个表达式。宏调用是一个表达式(println!有返回值?待研究)。我们用来创建新作用域的大括号(代码块),{},也是一个表达式
具有返回值的函数
在 Rust 中,函数的返回值等同于函数体最后一个表达式的值
使用 return 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。
正确:
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1
}
错误:
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
注释
在 Rust 中,注释必须以两道斜杠开始,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 //
控制流
if 表达式
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
代码中的条件 必须 是 bool 值,否则报错
Rust 并不会尝试自动地将非布尔值转换为布尔值
使用 else if 处理多重条件
如果有多于一个 else if 表达式,最好重构代码。为此,第六章会介绍一个强大的 Rust 分支结构(branching construct),叫做 match。c++的switch case ?
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
在 let 语句中使用 if
let condition = true;
let number = if condition {
5
} else {
6
};
如上if 的每个分支的可能的返回值都必须是相同类型
使用循环重复执行
Rust 有三种循环:loop、while 和 for。
使用 loop 重复执行代码
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
通过分号结束赋值给 result 的语句
while 条件循环
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number - 1;
}
println!("LIFTOFF!!!");
}
使用 for 遍历集合
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index = index + 1;
}
}
但这个过程很容易出错;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
大部分 Rustacean 也会使用 for 循环。这么做的方式是使用 Range,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
浙公网安备 33010602011771号