从零学Rust - 变量与数据类型
学习任何一门语言,都需要了解一些基本编程概念在该语言中的特点,首先来学习下变量和一些基础的数据类型。
变量与常量
程序中通过变量对状态进行维护,变量一般分为可变的变量,以及不可变的变量,后者我们一般称为常量。可变的变量为程序带来灵活性,常量为编程提供安全性。
变量 let & mut
使用 let 关键字声明和初始化一个变量 x,这个过程称为:变量绑定。
在 Rust 中通常用 绑定 而不是 赋值 来描述这个过程,与 Rust 的一个核心原则“所有权”有关:任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人。
fn main {
// Rust 的变量在默认情况下是不可变的, 使用 let 关键字声明
let x = 5;
println!("The value of x is: {x}");
// 二次赋值,会报错 cannot assign twice to immutable variable
x = 10;
println!("The value of x is: {x}");
}
Rust 中声明可变的变量要加个 mut
关键字。
fn main {
let mut x = 5;
println!("The value of x is: {x}");
// 或 println!("The value of x is: {}", x);
// 有多个值 println!("The value of x is: {}、{}", x, y);
x = 10;
println!("The value of x is: {x}");
}
// The value of x is: 5
// The value of x is: 10
未使用的变量会有警告,可以在前面加上一个下划线去掉报警。
fn main() {
let _x = 5;
let y = 10;
}
// help: if this is intentional, prefix it with an underscore: `_y`
常量 const
使用 const
关键字声明一个常量:
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量与 let 的区别
- const 必须显式注明值的类型,
const x = 100
报错:provide a type for the constant - 没有
const mut x = 1
,mut
不能用于const
,常量始终不可变 - 常量可以在任何作用域中声明,包括全局作用域
- 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
- 命名规范:常量 SCREAMING_SNAKE_CASE,局部变量 snake_case
隐藏 Shadowing
变量会隐藏(或者说遮蔽)前面声明的同名变量。
fn main() {
// 用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
// The value of x in the inner scope is: 12
// The value of x is: 6
// 在这里用花括号 { } 创建了一个局部作用域
// 在声明变量时也能通过 { } 初始化
let a = 100;
let b = {
let c = 20;
a + c // 这里没有分号 ;
}; // 这里有分号 ;
用处在于:如果你在某个作用域内无需再使用之前的变量,就可以重复的使用变量名字
隐藏与将变量标记为 mut
是有区别的:不能改变可变变量的类型
// ok
let spaces = " ";
let spaces = spaces.len();
// 编译时错误
let mut spaces = " ";
spaces = spaces.len(); // expected `&str`, found `usize`
数据类型
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。
// 能够直接通过值和上下文中的使用方式推导出类型
let a = 34;
let b = a + 10;
// 编译器还不够智能!当类型可能有多种时,不能直接推导出,必须显式的增加类型注解
// “字符串”.parse() 将字符串解析为数字
let guess: u32 = "42".parse().expect("Not a number!");
// 不加则报错 error[E0282]: type annotations needed
// 类型标注
let twenty_one: i32 = 21;
// 通过类型后缀的方式进行类型标注:22是i32类型
let twenty_two = 22i32;
Rust 中的数据类型可分为:标量类型(scalar)、复合类型(compound)
- 标量类型(代表一个单独的值):数值类型(整型、浮点型)、布尔类型、字符类型
- 两个原生的复合类型:元组、数组
还有一些其他的复合类型、集合类型等,这些类型在之后进行了解。
整型 integer
分为有符号(i
)和无符号(u
)两种,长度包括 8/16/32/64/128 比特。
例如:u8
代表0到 255,i8
代表 -128到127。
另外还有两种类型:isize
和 usize
,依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。数字类型默认是 i32
。isize
或 usize
主要作为某些集合的索引。
整型字面量的写法包括:
- Decimal (十进制):98_222
- Hex (十六进制):0xff
- Octal (八进制):0o77
- Binary (二进制):0b1111_0000
- Byte (单字节字符)(仅限于u8):b'A'
其中数字里面可以加下划线方便读数,比如 121_000 其实就是 121000
当将变量修改为超出它定义的范围时,会出现整型溢出(integer overflow)
let mut a: u8 = 34;
a = 256;
// error: literal out of range for `u8`
// the literal `256` does not fit into the type `u8` whose range is `0..=255`
整数除法会向零舍入到最接近的整数:
let truncated = -5 / 3; // 结果为 -1
浮点型 float
带小数点的数字,即浮点数的类型,包括 f32
和 f64
(默认),所有的浮点型都是有符号的。
let a: f32 = 23.23232323;
// 只有同样类型,才能进行加、减、乘、除、取余的数值运算
let b = a + 10; // cannot add `{integer}` to `f32`
let b = a + 10.0; // ok
浮点数采用 IEEE-754 标准表示。f32
是单精度浮点数,f64
是双精度浮点数。
布尔型 bool
即 true
或 false
,用 bool
表示,主要用在条件表达式内。
let t = true;
let f: bool = false; // 显式声明
字符类型 char
Rust 的 char
类型是语言中最原生的字母类型。
let c = 'z';
let z: char = 'ℤ'; // 显式声明
let heart_eyed_cat = '😻';
用单引号声明 char
字面量,使用双引号声明字符串字面量。
char
类型的大小为4个字节,并代表了一个 Unicode 标量值(包含从 U+0000
到 U+D7FF
和 U+E000
到 U+10FFFF
在内的值)。有效的 char
值包括:带变音符号的字母、中文、日文、韩文等字符、emoji以及零长度的空白字符。
元组 tuple
元组是将多个其他类型的值组合进一个复合类型的主要方式,长度固定不可变。
// 用括号()包含多个类型进行定义,绑定值也是对应的结构
let tup: (i32, f64, u8) = (500, 6.4, 1); // ok
// 必须一一对应,error[E0308]: mismatched types
// expected a tuple with 3 elements, found one with 2 elements
let tup: (i32, f64, u8) = (500, 6.4);
// 可以嵌套
let tup: (i32, f64, u8, (u8, u8)) = (500, 6.4, 1, (2, 2));
// 使用模式匹配(pattern matching)来解构(destructure)元组值
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
// 可以解构为可变变量
let (x, mut y) = (true, false);
y = true;
assert_eq!(x, y);
// 使用 . 点 + 索引值来访问单个元素
let val = tup.0; // 500
// error[E0609]: no field `3` on type `({integer}, {float}, {integer})`
let val = tup.3;
// 不能通过数组下标的形式
// error[E0608]: cannot index into a value of type `({integer}, {float}, {integer})`
let val = tup[0];
数组 array
- 每个元素的类型必须相同
- 数组长度是固定的
- 数组在栈(stack)而不是在堆(heap)上为数据分配空间
// 写在方括号内,逗号分隔
let a = [1, 2, 3];
// 显式的定义类型
let a: [i32, i32, i32] = [1, 2, 3]; // 错误 error[E0423]: expected value, found builtin type `i32`
// 正确的方式: [元素类型; 元素数量]
let a: [i32; 3] = [1, 2, 3];
// [初始值; 元素个数] 创建相同元素的数组
let a = [3; 5]; // 元素的长度为5,每个元素的值都是3
// error[E0308]: mismatched types
// expected integer, found floating-point number
let a = [1, 2, 3.2]; // 每个元素的类型必须相同
// error[E0277]: `[{integer}; 3]` doesn't implement `std::fmt::Display`
// `[{integer}; 3]` cannot be formatted with the default formatter
print!("{}\n", a); // 不能直接打印
print!("{:?}\n", a); // 正确方式 加上 :?
其他
单元类型
单元类型就是 ()
,唯一的值也是 ()
。它作为一个值用来占位,但是完全不占用任何内存。
比如常用的入口函数 fn main()
没有返回值。没有返回值的函数在 Rust 中是有单独的定义的:发散函数(diverge function)
,即无法收敛的函数,它返回的其实就是这个单元类型。