基本类型
Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:
- 数值类型:有符号整数 (
i8
,i16
,i32
,i64
,isize
)、 无符号整数 (u8
,u16
,u32
,u64
,usize
) 、浮点数 (f32
,f64
)、以及有理数、复数
有符号整数:二进制补码表示,包含正负数;每个有符号类型规定的数字范围是 -(2^n - 1) ~ 2^n - 1 - 1;i8范围为-128~127;isize(指针大小,32位系统=i32,64位系统=i64)
无符号整数:仅表示非负数;u8范围为0~255
浮点数:小心浮点数陷阱
- 字符串:字符串字面量和字符串切片
&str
- 布尔类型:
true
和false
- 字符类型:表示单个 Unicode 字符,存储为 4 个字节;Rust 的字符只能用
''
来表示,""
是留给字符串的。
Rust 的字符不仅仅是 ASCII
,所有的 Unicode
值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。Unicode
值的范围从 U+0000 ~ U+D7FF
和 U+E000 ~ U+10FFFF
。
- 单元类型:即
()
,其唯一的值也是()
数字运算
fn main() {// 编译器会进行自动推导,给予twenty i32的类型let twenty = 20;// 类型标注let twenty_one: i32 = 21;// 通过类型后缀的方式进行类型标注:22是i32类型let twenty_two = 22i32;// 只有同样类型,才能运算let addition = twenty + twenty_one + twenty_two;println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);// 对于较长的数字,可以用_进行分割,提升可读性let one_million: i64 = 1_000_000;println!("{}", one_million.pow(2));// 定义一个f32数组,其中42.0会自动被推导为f32类型let forty_twos = [42.0,42f32,42.0_f32,];// 打印数组中第一个值,并控制小数位为2位println!("{:.2}", forty_twos[0]);
}
序列
Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5
,生成从 1 到 4 的连续数字,不包含 5 ;1..=5
,生成从 1 到 5 的连续数字,包含 5,它的用途很简单,常常用于循环中:
for i in 1..=5 {println!("{}",i);
}
常量(constant)
- 常量不允许使用
mut
。常量不仅仅默认不可变,而且自始至终不可变,因为常量在编译完成后,已经确定它的值。 - 常量使用
const
关键字而不是let
关键字来声明,并且值的类型必须标注。
// Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,
// 另外对数字字面量可插入下划线以提高可读性
const MAX_POINTS: u32 = 100_000;
变量可变
还是第一次听这种说法🤭
Rust 的变量在默认情况下是不可变的。前文提到,这是 Rust 团队为我们精心设计的语言特性之一,让我们编写的代码更安全,性能也更好。当然你可以通过 mut
关键字让变量变为可变的,让设计更灵活。
fn main() {let x = 5;println!("the value of x is :{}", x);let x = 4;println!("the value of x is :{}", x);x = 6;println!("the value of x is :{}", x);
}
这样会得到报错信息:
(base) PS D:\000zyf\Learning\rust_learn\variables> cargo runCompiling variables v0.1.0 (D:\000zyf\Learning\rust_learn\variables)
error[E0384]: cannot assign twice to immutable variable `x`--> src\main.rs:6:5|
4 | let x = 4;| - first assignment to `x`
5 | println!("the value of x is :{}", x);
6 | x = 6;| ^^^^^ cannot assign twice to immutable variable|
help: consider making this binding mutable|
4 | let mut x = 4;| +++For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
具体的错误原因是 cannot assign twice to immutable variable x
(无法对不可变的变量进行重复赋值),因为我们想为不可变的 x
变量再次赋值。
这种错误是为了避免无法预期的错误发生在我们的变量上:一个变量往往被多处代码所使用,其中一部分代码假定该变量的值永远不会改变,而另外一部分代码却无情的改变了这个值,在实际开发过程中,这个错误是很难被发现的,特别是在多线程编程中。
不过要想让某一变量可以被改变也很简单,只需要用mut
就好,像下面这样:
fn main() {let x = 5;println!("the value of x is :{}", x);let mut x = 4;println!("the value of x is :{}", x);x = 6;println!("the value of x is :{}", x);
}// (base) PS D:\000zyf\Learning\rust_learn\variables> cargo run
// Compiling variables v0.1.0 (D:\000zyf\Learning\rust_learn\variables)
// Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.04s
// Running `target\debug\variables.exe`
// the value of x is :5
// the value of x is :4
// the value of x is :6
补充
在编写代码进行学习的时候,无意中将第二个变量前面也加了let
,按理来第一次没加mut
这个代码应该报错的,但却成功运行了,又稍微了解了一下:
Rust变量遮蔽与可变性解析
工作正常的代码分析
fn main() {let x = 5; // x 是不可变的,值为5println!("the value of x is :{}", x);let mut x = 4; // 创建一个新的可变变量x,遮蔽(shadow)了原来的xprintln!("the value of x is :{}", x);x = 6; // 可以修改这个可变的xprintln!("the value of x is :{}", x);
}
这段代码能够正常工作是因为:
第一个 x 是不可变的,值为5
使用 let mut x = 4 声明了一个全新的变量 x,它遮蔽了之前的 x
这个新的 x 是可变的(mut),所以后续的 x = 6 赋值操作是合法的
无法工作的代码分析
fn main() {let mut x = 5; // x 是可变的,值为5println!("the value of x is :{}", x);let x = 4; // 创建一个新的不可变变量x,遮蔽了原来的xprintln!("the value of x is :{}", x);x = 6; // 错误!新的x是不可变的,不能被修改println!("the value of x is :{}", x);
}
这段代码无法工作的原因是:
第一个 x 虽然是可变的,但它随后被遮蔽了
第二个 let x = 4 创建了一个新的变量,默认是不可变的(没有使用mut关键字)
当尝试 x = 6 时,编译器会报错,因为当前作用域中的 x 是不可变的
核心概念
变量遮蔽:在Rust中,使用let关键字重新声明同名变量会创建一个全新的变量,完全遮蔽之前的变量
变量可变性:每个新变量的可变性由其声明时是否使用mut关键字决定,与被遮蔽的同名变量无关
要修复第二个例子,可以将第二个声明改为 let mut x = 4;,这样后续的赋值操作就合法了。
变量遮蔽的用处在于,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。
选择可变还是不可变,更多的还是取决于你的使用场景,例如不可变可以带来安全性,但是丧失了灵活性和性能(如果你要改变,就要重新创建一个新的变量,这里涉及到内存对象的再分配)。而可变变量最大的好处就是使用上的灵活性和性能上的提升。
例如,在使用大型数据结构或者热点代码路径(被大量频繁调用)的情形下,在同一内存位置更新实例可能比复制并返回新分配的实例要更快。使用较小的数据结构时,通常创建新的实例并以更具函数式的风格来编写程序,可能会更容易理解,所以值得以较低的性能开销来确保代码清
忽略未使用变量
告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头
fn main() {let _x = 5;let y = 10;
}// warning: unused variable: `y`
// --> src/main.rs:3:9
// |
// 3 | let y = 10;
// | ^ help: 如果 y 故意不被使用,请添加一个下划线前缀: `_y`
// |
// = note: `#[warn(unused_variables)]` on by default
变量解构
let
表达式不仅仅用于变量的绑定,还能进行复杂变量的解构:从一个相对复杂的变量中,匹配出该变量的一部分内容:
fn main() {let (a, mut b): (bool,bool) = (true, false);// a = true,不可变; b = false,可变println!("a = {:?}, b = {:?}", a, b);b = true;assert_eq!(a, b);
}
// a = true, b = false
解构式赋值
struct Struct {e: i32
}fn main() {let (a, b, c, d, e);(a, b) = (1, 2);// _ 代表匹配一个值,但是我们不关心具体的值是什么,因此没有使用一个变量名而是使用了 _[c, .., d, _] = [1, 2, 3, 4, 5];Struct { e,.. } = Struct { e: 5 };assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);println!("{}",a)
}