变量
Rust是静态强类型语言,所有的变量都有严格的编译期语法检查。
变量声明
格式:
let variable : i32 = 100;
rust要声明的变量前置,而对类型后置。应为最重要的是变量本身,而类型是个附属的额外描述,并非不可或缺,如果编译器可以通过上下文环境自动分析出这个变量的类型,那么就可以不写,并且每个变量都必须合理初始化之后才能被使用。
声明的变量默认是只读的,如果直接编译,会报错re-assignment of immutable variable
let mut x=5; //设定变量可写
也可声明多个变量:
let (mut a, mut b)=(1,2);
let Point {x:ref a,y:ref b}=p
在Rust中,一般把生命局部变量并初始化的语句称为“变量绑定”,与“赋值初始化”有所区别。
变量类型
一般数据类型bool let x = true;
char
设计目的是为了描述任意一个unicode字符,占4个字节。 let love = '❤';
整数类型
字面量后面可以跟后缀,代表数字的具体类型
let var1 = 123usize; // 1变量是usize类型
let var2 = 0x_ff_u8; // 2变量是u8类型
let var3 = 32; // 不写类型,默认为 i32 类型
对于溢出的处理:
在debug模式下,会自动插入整数溢出检查,一旦发生溢出,引发panic。
在release模式下不检查整数溢出,采用自动舍弃高位的方式。浮点数
两种类型,f32和f64
在标准库中,有一个std::num::FpCategory枚举,表示了浮点数可能的状态:
enum FpCategory {
Nan, //不是数字,举例:0.0f32 / 0.0
Infinite, //无穷大,举例:1.0f32 / 0.0
Zero, //0
Subnormal, //当浮点数小到无法在指定范围内合理表达的程度,进入此状态。精度会低些
Normal, //表示正常浮点数
}指针类型
rust没有GC(内存回收机制),对数据的组织操作有更多的自由度。
智能指针
类型转换
rust的类型转换为了防止隐藏的bug,使用as关键字将转换显式地标记出来,且只允许编译器认为合理的类型转换。 fn main() {
let i = 42;
// 先转为 *const i32,再转为 *mut i32
let p = &i as *const i32 as *mut i32;
println!("{:p}", p);
}
复合数据类型
在其他类型的基础上形成的更复杂的组合关系,数组和字符串放到之后讲。tuple
元组类型。 let a = (0,); // a是一个元组,它有一个元素
let a = ("b", (1i32, 2i32));
有两种方式可以访问元组内部元素,模式匹配(Rust的特色之一,后文讲)和数字索引。 let p = (1i32, 2i32);
let (a, b) = p;
let x = p.0;
let y = p.1;
println!("{} {} {} {}", a, b, x, y);
元组内部也可以一个元素都没有,叫unit(单位类型),占用0内存空间。 let empty : () = ();
struct
结构体类型 struct Point{
x: i32,
y: i32,
}
初始化 fn main(){
let p = Point{x:0,y:0};
println!("Point is at {} {}",p.x,p.y);
}
如果变量名恰巧一致,可以使用简略写法 fn main() {
// 刚好局部变量名字和结构体成员名字一致
let x = 10;
let y = 20;
// 下面是简略写法,等同于 Point { x: x, y: y },同名字的相对应
let p = Point { x, y };
println!("Point is at {} {}", p.x, p.y);
}
如果类型相同,也可以进行绑定。 fn main() {
let p = Point { x: 0, y: 0};
// 声明了px 和 py,分别绑定到成员 x 和成员 y
let Point { x : px, y : py } = p;
println!("Point is at {} {}", px, py);
// 同理,在模式匹配的时候,如果新的变量名刚好和成员名字相同,可以使用简写方式
let Point { x, y } = p;
println!("Point is at {} {}", x, y);
}
语法糖,用一种简化的语法复制使用另外一个struct的部分成员。 struct Point3d{
x:i32,
y:i32,
z:i32,
}
fn default() -> Point3d{
Point3d{x:0,y:0,z:0}
}
tuple struct
它就像是tuple和struct的混合。区别在于tuple struct有名字,而它们的成员没有名字。 struct point(
o:i32,
1:i32,
2:i32,
)
tuple、struct和tuple struct区别
struct T1 {
v: i32
}
// define tuple struct
struct T2(i32);
fn main() {
let v1 = T1 { v: 1 };
let v2 = T2(1); // init tuple struct
let v3 = T2 { 0: 1 }; // init tuple struct
let i1 = v1.v;
let i2 = v2.0;
let i3 = v3.0;
}
当tuple struct中只包含一个元素的时候就是newtype idiom,可以非常方便地在一个类型的基础上创建一个新的类型。 fn main() {
struct Inches(i32);
fn f1(value : Inches) {}
fn f2(value : i32) {}
let v : i32 = 0;
f1(v); // 编译不通过,'mismatched types'
f2(v);
}
经修改: fn type_alias() {
type I = i32;
fn f1(v : I) {}
fn f2(v : i32) {}
let v : i32 = 0;
f1(v);
f2(v);
}
enum
如果说tuple、struct、tuple struct在Rust中代表的是多个类型的”与“关系,那么enum(枚举)代表的就是“或“关系。 enum Number{
Int(i32),
Float(f32),
}
在Rust中,emun和struct为内部成员创建了新的namespace,可通过::访问内部成员,不同struct或enum中的重名元素也不会互相冲突。 enum Message{
Quit,
ChangeColor(i32,i32,i32),
Move{x:i32,y:i32},
Write(String),
}
let x:Message = Message::Move{x:3,y:4};
enum Point{
Move{a:i32,b:i32},
}
let y:Point = Point:Move{a:3,b:4};
变量遮蔽
Rust允许在同一个代码块中声明同样名字的变量。但这样会使前面声明的变量被遮蔽。
fn main() {
let x = "hello";
println!("x is {}", x);
let x = 5;
println!("x is {}", x);
}
被遮蔽之后就无法访问前面一个变量。这种方式是十分有效,例如类型转换,修改可变性。
fn main() {
let mut v = Vec::new(); // v 必须是mut修饰,因为我们需要对它写入数据
v.push(1);
v.push(2);
v.push(3);
let v = v; // 从这里往下,v成了只读变量,可读写变量v已经被遮蔽,无法再访问
for i in &v {
println!("{}", i);
}
}
反之亦然
fn main() {
let v = Vec::new();
let mut v = v;
v.push(1);
println!("{:?}", v);
}
变量推导
将数据类型交由编译器自动推导
fn main() {
let player_scores = [
("Jack", 20), ("Jane", 23), ("Jill", 18), ("John", 19),
];
// players 是动态数组,内部成员的类型没有指定,交给编译器自动推导
let players : Vec<_> = player_scores
.iter()
.map(|&(player, _score)| {
player
})
.collect();
println!("{:?}", players);
}
类型别名
type Age = u32
fn grow(age: Age, year: u32) -> Age {
age + year
}
fn main() {
let x : Age = 20;
println!("20 years later: {}", grow(x, 20));
}
静态变量
如果想使用复杂的静态变量初始化,可以使用lazy_static库
fn main() {
//局部变量声明,可以留待后面初始化,只要保证使用前已经初始化即可
let x;
let y = 1_i32;
x = 2_i32;
println!("{} {}", x, y);
//全局变量必须声明的时候初始化,因为全局变量可以写到函数外面,被任意一个函数使用
static G1 : i32 = 3;
println!("{}", G1);
//可变全局变量无论读写都必须用 unsafe修饰
static mut G2 : i32 = 4;
unsafe {
G2 = 5;
println!("{}", G2);
}
//全局变量的内存不是分配在当前函数栈上,函数退出的时候,并不会销毁全局变量占用的内存空间,程序退出才会回收
}
常量
const GLOBAL: i32 = 0
常量不允许使用mut关键字修饰。与静态变量的最大区别在于编译器并不一定会给const常量分配内存空间,在编译过程中,它很可能会被内联优化,因此,用户千万不要用hack的方式,通过unsafe代码去修改常量的值,这么做是没有意义的。