入门Rust"/>
第N次入门Rust
文章目录
- 前言
- 2.1 编码约定
- 2.2 注释
- 2.3 变量 和 变量可变性 和 常量
- 2.3.1 变量和变量可变性
- 2.3.2 常量
- 2.4 数据类型
- 2.4.1 标量
- 整型
- 浮点型
- 数值运算
- 布尔类型
- 字符类型
- 2.4.2 复合类型
- 元组类型
- 数组类型
- 2.5 表达式和语句
- 2.6 函数
- 2.7 控制流
- 2.7.1 if表达式
- 2.7.2 循环
- loop表达式
- while
- for
- 2.8 小结
前言
这一篇主要介绍基本的Rust面向过程的基本语法~
建议有其它编程语言基础,一些与常见编程语言雷同的概念和语法就不重复描述了。
2.1 编码约定
- rust与cpp等类似,变量和参数都是采用小写+下划线(
_
)的方式命名。 - 类型采用大驼峰。
- 常量忘了有没有要求了,反正我习惯采用大写+下滑线(
_
)的方式命名。
2.2 注释
// 这是单行注释/*
这是注释块
*//// 文档注释行注释//! 这是
//! 一个
//! 文档注释块
- 文档注释:
- 文档注释的行注释:以三个斜线开头:
/// 文档注释行注释
; - 文档注释的块注释:用
//!
开头表示;
- 文档注释的行注释:以三个斜线开头:
2.3 变量 和 变量可变性 和 常量
变量和常量的概念就不解释了。
2.3.1 变量和变量可变性
- 变量可变性:指通过语法声明一个变量的值能否修改。变量是可以指定为可变和不可变两种情况的,默认情况下是不可变的。
- 当一个变量被声明为可变,则可以对变量进行读操作和写操作;当一个变量被声明为不可变,则只能对这个变量进行读操作。
- 声明变量的过程Rust称为绑定:将一个值绑定到一个变量上面。 这个概念很重要,因为这与后面理解所有权息息相关,可以先粗略地认为一个值只能绑定在一个变量或者一个值上面,同时一个变量只能绑定一个值,这可以把变量与值的关系描绘成一棵树:树的根一定是变量,其下面一定是一个值,如果这个值是一个HashMap或者其它自定义结构体之类的复杂结构,则可以认为它又是其成员(字段)的根(只是方便理解,实际rust的内部实现应该不是这样的)。
|-变量名|-值1(如果是个HashMap)|-值11|-值12|-...|-值1N
- 当一个变量是不可变的时候,它下面关联的所有值也不能进行写操作,只能进行读操作(即变量的可变性覆盖它绑定的值及关联值,把它们当做一个整体)。
- 当然这种通过可变性控制能够进行写操作的约束实际上是通过编译器完成的:编译器检查到一个变量是不可变的,则当后面发现有对它的写操作时,就会报错。
- 可以把一个变量的值绑定到另外一个变量上,以达到更改可变性的目的(即原来值绑定在不可变变量A上,该值无法修改,当把值绑定到另一个变量B上时,声明B为可变的,则后面操作B时就可以修改这个值了,反之亦然,当然这里还涉及到所有权变更的问题,这个后面再讲,现在先理解上面的变量和值关系的模型会对后面理解有比较大的帮助)。
- 隐藏(shadowing):用一个已使用的变量名重新绑定一个值的语法,这种语法的作用在于在作用域内后续使用该变量名会被指另一个变量上,从而实现改变某一个变量的可变性、或重复使用一个变量名来表示不同类型变量的作用。
- 变量声明语法:
let var_name1 = value1; // 声明不可变变量,并绑定值,通过上下文推导变量类型 let var_name2: TypeName1 = value2; // 声明不可变变量,并绑定值,显式声明变量类型 let mut var_name3 = value3; // 声明可变变量,并绑定值,通过上下文推导变量类型 let mut var_name4: TypeName2 = value4; // 声明可变变量,并绑定值,显式声明变量类型let mut var_name1 = value5; // 隐藏var_name1原来的值,重新绑定新值,并通过上下文推导变量类型 let var_name3 = var_name4; // 隐藏var_name1原来的值,重新绑定新值,这里注意旧值和新值的类型可以不一样
- 类型推导:这是现代编程语言的一个厉害的功能,它可以根据上下文信息(不限于是前面的逻辑还是后面的逻辑)推导出变量的类型(当推导不出来的时候就会报错),这应该也是rust编译慢的其中一个原因吧。
- 绑定必须要使用关键字
let
,没有let
的话,语法就变成了赋值或者值转移(这个讲所有权的时候会解释)。 mut
用来修饰变量,表示变量是一个可变的变量。- 隐藏和将变量标记为
mut
有本质的区别,隐藏实际上是重新声明了一个新的变量,只是这个变量的变量名跟之前某个变量的变量名重复,也就是说此时会多出一个新的变量(被隐藏的那个变量还是存在的且变为不可见)。
2.3.2 常量
- 不可变变量与常量的区别:内存中存放位置和初始化等方面有本质区别;
- 声明常量时需要显式给出值的类型和初始值常量表达式(必须保证可以在编译期计算出来),因为常量声明后就变不了。
- 常量声明以后可以在作用域中使用。
- 常量的声明需要使用
const
关键字而不是let
关键字:const I_AM_CONST: i32 = 10;
2.4 数据类型
rust中的每一个值都属于一种数据类型,数据类型分为两类:
- 标量(scalar):单独的值,包括整型、浮点型、布尔类型、字符类型。(与其它编程语言大同小异)
- 复合(compound):多个值组合的类型,原生复合类型包括元祖和数组。
先说明:数据是标量还是复合类型与值的复制转移行为无关(等后面将所有权的时候会细讲)。
2.4.1 标量
整型
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
-
其中
iszie
和usize
与操作系统架构有关,比如32位时长度都是32位,64位时长度都是64位。适合作为集合索引(使用集合的时候索引也都是usize
类型)。 -
整型默认类型为
i32
,哪怕在64位操作系统上同样是最快的。 -
整型字面量包括以下情况:
- 十进制:
1020
- 十六进制:
0xff
- 八进制:
0o77
- 二进制:
0b11110000
- 单字节字符:
b'A'
- 十进制:
-
除了单字节字符字面量,其他整形字面量可以在数字中间添加
_
作为分隔符以方便度数,如100_000
、0b1111_0000
。 -
关于溢出,在debug模式下,溢出会抛出panic,在release下,rust不会检查溢出,而是按照补码的运算的规则继续运算。
浮点型
f32
:32位,即floatf64
:64位,即double- 在现代CPU中,两者的运算速度几乎一样。
数值运算
rust中的所有数字类型都支持基本数学运算,加减乘除和求余:+-*/%
。与其它编程语言基本一致。
布尔类型
bool
包括true
和false
。
字符类型
- rust的字符类型(
char
),使用单引号指定,大小为四个字节(与整型的单字节字符有本质区别),并代表一个Unicode标量值(即与java类似)。 - 字符串为双引号包裹的串,字符串的数据结构后面会重点讲一下。
2.4.2 复合类型
元组类型
- 元素是一个将多个其他类型的值组合进一个复合类型的主要方式。
- 元组长度固定:一旦声明,其长度不会增大或缩小。
- 声明语法:
// 不声明类型,自动推导类型 let tup1 = (10, 20.0, false);// 显式声明类型 let tup2: (i32, f64, bool) = (10, 20.0, false);
- 元组模式匹配解构:
let tup1 = (10, 20.0, false); let (v1, v2, v3) = tup1;
- 元组可以通过
.下标
访问元素(下标从0开始):let tup1 = (10, 20.0, false); println!("{}", tup1.0); println!("{}", tup1.1);
数组类型
- 数组内的元素必须类型相同,且数组的长度固定,一旦声明,其长度不会增大或缩小。
- 声明语法:
// 不声明类型,自动推导类型 let arr1 = [10, 20, 30];// 显式声明类型和长度 let arr2: [i32; 5] = [10, 20, 30, 40, 50];// 显式声明初始值和长度 let arr3: [100; 5]; // [100, 100, 100, 100, 100]
- 通过下标(从0开始)访问元素:
let arr1 = [10, 20, 30]; let first = arr1[0];
- 数组越界属于运行时错误(runtime时会panic),编译期无法检测出来(实际上简单的检查还是会做的)。
- 数组的数据放在stack(栈)上而不是heap(堆)上。
- 数组中数据时连续存放的。
- 数组没有Vector灵活,Vector和数组类似:
- Vector由标准库提供,数组由Preclude提供(也是属于标准库)。
- Vector的长度可以改变。
- 在不确定使用数组还是Vector的时候,一般使用的是Vector。
2.5 表达式和语句
- 在rust中,语句(Statements)和表达式(Expressions)是有区别的:
- 表达式没有分号结尾,可以计算出值;
- 语句则是由分号的表达式,没有值返回;
- 可以使用
{}
包含一批语句和一个表达式,此时这些语句和表达式会在一个新的作用域中执行,并将表达式的结果作为作用域的返回,从而实现作用域隔离的目的。let x = 5;let y = {let x = 3;x + 1} // 此时y的值就是4
2.6 函数
- 函数语法:
fn function_name(param_name1: Type1, param_name2: Type2) -> ReturnType {// 语句1;// 语句2;// ...// 表达式 }
- 形参列表中的每个形参必须显式声明类型。
- 如果没有返回值,可以不写
-> ReturnType
。 - 可以在函数体中使用
return
关键字提前返回。 - 函数如果最后一句是一个表达式,则计算结果会作为返回值返回;如果是语句,则没有返回(编译器识别为空元组
()
表示不返回值)。 - rust的函数不一定要声明在调用位置的前面,只要函数在调用者可访问的作用域内,就能调用。
2.7 控制流
2.7.1 if表达式
- if表达式每个条件关联的代码块叫作分支(arm)。
- 基本的条件语句语法:
if bool表达式1 {// 分支1 } else if bool表达式2 {// 分支2 } else {// 分支3 }
- 在
let
语句中使用if表达式实现三目运算符java中?:
的功能:let x = if bool表达式1 {// 分支1表达式1 } else if bool表达式2 {// 分支2表达式2 } else {// 分支3表达式3 }
- 注意,需要保证每个分支返回值的类型要与接收变量的类型兼容。
- if表达式因为是表达式,所以可以有返回值,此时需要每个分支的返回值类型一致。
2.7.2 循环
循环支持break
、continue
。
loop表达式
loop
是无限循环。- 从循环返回结果:
loop
在使用break
时,可以使用break 表达式;
返回计算结果到循环体外,注意这种break
返回值的语法只能在loop
循环或其他breakable blocks
中使用。
let x = loop {// 语句break 100;
}
while
whlie
不是表达式,所以循环无法使用break
返回结果。
while bool表达式 {// 语句
}
for
for
循环可以用于遍历迭代器。for
不是表达式,所以无法使用break
返回结果。- 如果要编译一个数列,可以使用
Range
(一种标准库提供的类型),用来生成从一个数字开始到另一个数字之前结束的所有数字序列。可以使用语法糖a..b
创建Range类型值,表示[a,b)
。rev
方法可以反转Range
。
let arr = [1,2,3,4,5]
for x in arr.iter() {// x是一个 i32 类型的引用// 语句
}for y in 1..21 {// 语句
}for z in (1..4).rev() {// 语句
}
2.8 小结
这一篇文章主要介绍了Rust面向过程编程的基本语法,如变量常量声明、基础类型、控制流(顺序、条件、循环)等语法。
对比其它编程语言,Rust的基础语法比较特别的地方包括但不限于:
- 变量具有可变性这一属性;
- 强调表达式和语句是不一样的:表达式会运算并返回结果,语句可以认为是返回结果为
()
的表达式;
Rust之所以这样设计是统一语法的行为,从而降低开发者学习和使用Rust的心智负担(即相同的语法和关键字用在不同地方作用是类似的)。
实际上本文很多知识点的说法与Rust真正实现相比相差比较大,本文的说法实际上是希望拥有其它编程语言基础的读者能快速了解Rust的语法。想要正确理解Rust的设计可以读一下张汉东老师的《Rust编程之道》,建议先过一遍Rust的语法再看。
更多推荐
第N次入门Rust
发布评论