大部分语言都有一组保留的关键词,这些关键词只能由语言本身进行使用,在对函数或者变量进行命名时需注意避开关键词,rust大部分关键字都有特殊的意义,如match
表示模式匹配,一些关键字目前没有相应的功能,如:abstract、do
等,Rust现在虽然没有给其增加功能,但却是为将来可能添加的功能所保留的。
参照A - 关键字 - Rust 程序设计语言 简体中文版 (kaisery.github.io)给出的关键词,大致有个印象即可
Rust
的变量通过let
关键词进行声明,如
let x=5;
与大部分语言不同,rust的变量在赋值后默认是不可变的(immutable),例如以下代码
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
//
//注:Rust的注释以两个斜杠开始注释,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 `//`
当你尝试编译这段代码时,编译器将抛出cannot assign twice to immutable variable
,因为你尝试对不可变变量 x 赋第二个值.
当然变量只是默认不可变,但仍可以通过在变量名之前加mut
来使其可变,例如:
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
此时则能正常编译
与变量相对应的即为常量,常量(constants)与一个不允许改变的值相绑定,常量通过const
关键词声明,需要注意的是,常量不可使用mut
来使其可变,不光如此常量还总是不可变的,而且常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值,并且需要显式地注明值的类型(这点之后再说)。如:const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
Rust存在一个shadowing机制
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {}", x);
//当该作用域结束时,内部 shadowing 的作用域也结束了, x 又返回到6
}
println!("The value of x is: {}", x);
}
//The value of x in the inner scope is: 12
//The value of x is: 6
如上所示,我们首先声明了x,其值为5,但紧接着我们再次定义一个与之前变量同名的新变量,其值为原值加一,此时我们称之为第一个变量被第二个 隐藏 了,程序使用这个变量时会看到第二个值。
shadowing与mut
是有区别的,当我们不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let ,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的.
//改变类型
let spaces = " ";
let spaces = spaces.len();
//编译错误
let spaces = " ";
spaces = spaces.len();
前面我们说在const声明时必须注明值的类型,因为Rust 是静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。所以现在我们来了解一下Rust的基本数据类型,Rust有两类数据类型子集:标量(scalar)和复合(compound)。
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语言,存在有无符号的区别。其中isize
和 usize
类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的.每一个有符号的变体可以储存包含从 − ( 2 n − 1 ) -(2^{n-1}) −(2n−1) 到 2 n − 1 − 1 2^{n-1}-1 2n−1−1 在内的数字,无符号的变体可以储存从 0 到 2n - 1 的数字。
fn main() {
let x:i32 = 2; // i32
let y: u32 = 3; // u32
let z: usize = 4; //usize
}
整型的值可以是多种数字类型的数字字面值加上类型后缀来同时声明类型并赋值,例如 57u8 来指定类型,当然这对于浮点数也同样生效,同时也允许使用 _ 做为分隔符以方便读数,例如1_000,它的值与你指定的1000 相同
fn main() {
let x=2i32; // i32
let y: u32 = 3; // u32
let z: usize = 4_000; //usize
}
Rust具有两个原生的浮点类型,f32
和f64
,默认类型为f64
,所有的浮点型都是有符号的。
浮点数采用 IEEE-754 标准表示。 f32 是单精度浮点数, f64 是双精度浮点数。
fn main() {
let x = 2.0; // 默认f64
let y: f32 = 3.0; // f32
let z = 4.0f64; //f64
}
仅有true
和false
两个值,可不显式指定类型注解
fn main() {
let t = true;
let f: bool = false; // 显式指定类型注解
}
char
为rust中最原生的字母类型,char的大小为四个字节,代表了一个Unicode的标量值(Unicode Scalar Value).它可以比ASCII表示更多的内容。中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '';
}
Rust有两个原生的复合类型:元组(tuple)和数组(array)
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。**元组长度固定:一旦声明,其长度不会增大或缩小。**使用包含在圆括号中的逗号分隔的值列表来创建一个元组,每个位置都有一个类型,类型不必相同。
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
tup
将变量绑定到整个元组上,为了从元组中获取单个值,可以通过模式匹配(pattern matching)来解构(destructure)元组。
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
我们也可以使用点号( .
)后跟值的索引来直接访问它们
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
**没有任何值的元组 () 是一种特殊的类型,只有一个值,也写成 () 。**该类型被称为单元类型(unit type),其值被称为单元值(unit value)。如果表达式不返回任何其他值,则会隐式返回单元值。
与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust中的数组长度是固定的
fn main() {
let a = [1, 2, 3, 4, 5];
}
当你想要在栈(stack)而不是在堆(heap)上为数据分配空间,或者是想要确保总是有固定数量的元素时,数组非常有用。但是array不如vector类型灵活。
在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量。
let a:[i32;5] = [1,2,3,4,5];
你还可以通过在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组
let a = [3;5];
//let a = [3,3,3,3,3];
数组是可以在堆栈上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素.
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
除了这两类基本类型外,Rust 里的其他数据类型都是由标量和复合类型构成的集合类型,如 Vec、HashMap
等,Vec
类似于python的list,但相对没python的list那么灵活,HashMap
类似于python的字典。
这里提一嘴,标量类型都是最基本的和内存结合最紧密的原生类型,运算效率非常高,可以视为 O ( 1 ) O(1) O(1),而复合类型则复杂一些,复杂度随其数据规模而变化。
到这里我们已经学会了如何进行变量的声明,但仅有变量而无逻辑处理是不够的,因此现在介绍Rust的控制流。Rust 代码中最常见的用来控制执行流的结构是 if 表达式和循环
if
表达式以if
关键词开头,后接一个条件,这个条件必须是bool
值,条件为真执行的代码位于紧跟条件之后的大括号中
if 条件 {
//为true时执行的语句
}
//注意这里的条件可以不需要用()包裹
与if
相配合使用的还有else
和else if
,通过配合可以实现多重条件
fn main() {
let condition = true;
let number = if condition { return 5; } else { return 6; };//if let同时使用完成赋值
let a = 1;
let b = 2;
if a>b {
println!(a);
}
else if a<b {
println!(b);
}
else{
println!(a-b);
}
println!("The value of number is: {}", number);
}
需要注意的是尽量不要使用过多的else if
表达式
if
常与逻辑运算符一起使用
名称 | 逻辑运算符 | 描述 | 范例 |
---|---|---|---|
逻辑与 | && |
所有的表达式结果为真则返回 true ,否则返回 false |
(1 > 10 && 11 > 10) 的结果为 false |
逻辑或 | || |
有一个表达式结果为真则返回 true ,否则返回 false |
(1 > 10 || 11 >10) 的结果为 true |
逻辑非 | ! |
如果表达式的结果为真则返回 false ,否则返回 true |
!(1 >10 ) 的结果为 true |
Rust 有三种循环: loop 、 while 和 for。
loop
关键字将重复执行一段代码直到你明确要求停止,格式为关键词loop
加上大括号包裹的循环体,使用break
关键词可从代码中跳出循环,与break
相对应的关键词为continue
。
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; //使用 break 关键字返回值 counter * 2
}
};
println!("The result is {}", result);
}
上例使用 break 关键字返回值 counter * 2
格式为关键词while
,加上条件,当条件为真,执行循环,循环体被包裹在紧跟条件之后的大括号中。
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
for
语句的格式为
for name in 取值列表
for 用户名 in 列表
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
fn main() {
for number in 1..4 {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
为了使代码更加简洁,我们可以将一些代码结合在一起并声明为函数,但是在介绍函数之前我们需要了解另一个概念–表达式和语句
Rust is an expression-based language
前面我们在讲if语句时有提到表达式的概念,现在我们来说说表达式和语句。语句(Statements)是执行一些操作但不返回值的指令,语句通过;
表示结束,表达式(Expressions)则计算产生并返回一个值,二者的区别在于是否有返回值
fn main() {
let x = 1; //语句
let x = {
let x = 3;
x + 1 //此处无;,则表达式将返回x+1,等同于return x+1;加上;则成为语句并报错
}; //表达式作为语句的一部分
let y = 6; //6本身就是一个表达式
println!("The value of y is: {}", y);
}
用大括号创建的一个新的块作用域也是一个表达式。表达式会计算出一个值,并且你将编写的大部分 Rust 代码是由表达式组成的。有了表达式与语句的概念后,我们来介绍函数的使用
函数通过关键词fn
声明,Rust 代码中的函数和变量名使用 snake case 规范风格。Rust 不关心函数定义于何处,只要定义了就行,函数声明的格式为
fn 函数名(参数) ->返回值类型{
函数体
---
---
返回值
}
参数是特殊变量,是函数签名的一部分,有多个参数时,使用,
进行分隔;函数的返回值默认等同于函数体最后一个表达式的值,也可以使用 return 关键字和指定值,从函数中提前返回;但大部分函数隐式的返回最后的表达式。其中参数可无,返回值也可无;但如有,参数必须声明类型,返回值无需命名,但必需指定返回类型
//没有参数,也没有返回值
fn another_function() {
println!("这是一个函数");
}
//有参数,无返回值
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {}{}", value, unit_label);
}
//有参数,有返回值
fn plus_one(x: i32) -> i32 {
x + 1//加;将报错,也可改为return x+1;
}
//函数的使用
fn main(){
let x = 1;
println!("x+1={}",plus_one(x));
let y = plus_one(4);
}
函数调用是一个表达式,因此函数的使用有表达式基本相同。
至此,我们学习了