Rust语言基础语法

通过一猜数游戏代码开发过程的学习,我们完成了猜数游戏的程序编写,但是对一些代码的语法细节却没有展开讲解。本章节将对猜数游戏涉及的Rust基础语法进行一次梳理。

1、变量声明

Rust局部变量(可以理解为声明在函数内部的变量)声明的形式是:

let mut(可选) 变量名称:数据类型(可选) = 变量值;

其中mut变量说明该变量的值可以重新绑定,而没有mut时,表示该变量值只能绑定一次,否则编译异常,如下代码所示:

fn main() {
    let x = 1; //1
    x = 2; //2
    let mut y: i32  = 0;//3
    y = 123;//4
    let x = y + 1;//5
}

第一行:声明变量x,类型为i32(通过=右侧的值可以推断x的数据类型为i32因此: i32数据类型声明省略,与语句let x: i32 = 1;完全相同;
第二行:再次对x绑定新值,编译出错,因为该变量未使用mut关键字修饰;
第三行:声明变量y为mut并绑定初始值为0;
第四行:再次对y绑定新值,因为有mut关键字修饰,表示这个变量可以多次绑定值,正常编译;
第五行:再次声明变量x,与第一行声明变量x重名,这时在第一行声明的x变量将不再有效。

2、常量声明

声明常量使用关键字const替换let,其他形式相同:

const TEST_VALUE: i32 = 1;

const修饰的常量,必须声明数据类型(例如本例中的: i32就是声明常量的代码)并同时绑定常量值。let和const都可以声明常量,两者的区别如下:

对比项 const let
声明时必须绑定值
声明时必须声明数据类型
可定义相同名称的变量或常量

在实际开发中,开发人员根据实际需要确定使用哪个关键字。

3、数据类型

Rust是静态类型(statically typed)语言, 也就是说在编译时就必须知道所有变量的类型。总的来说,Rust数据类型分为数值型、bool型、元组类等。
数值型

数据类型 长度(字节数) 取值范围 备注
i8 8 从-128到127 有符号
i16 16 从-32768到32767 有符号
i32 32 从-2147483648到2147483647 有符号
i64 64 从-9223372036854775808到9223372036854775807 有符号
i128 128 有符号
isize 依赖与计算机架构。64 位架构上它们是 64 位的, 32位架构上它们是 32 位的
u8 8 从0到255 无符号
u16 16 从0到65535 无符号
u32 32 从0到4294967295 无符号
u64 64 无符号
u128 128 无符号
usize 依赖于计算机架构。64 位架构上它们是 64 位的, 32位架构上它们是 32 位的
f32 32
f64 64 默认f64,例如:let x = 2.0;等同于let x: f64 = 2.0

bool型
bool型取值只有true或者false二者之一。
元组类型
元组是一种将多个不同类型的值组合进一个复合类型的主要方式。 元组长度固定: 一旦声明, 其长度不会增大或缩小。我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。 元组中的每一个位置都有一个类型, 而且这些不同值的类型也不必是相同的。 如下代码所示:

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);//定义元组
    let (x, y, z) = tup;//解构元组
    println!("{}", y);
    let five_hundred = tup.0;//通过索引访问元组中的数据
    let six_point_four = tup.1;
    let one = tup.2;
    println!("打印数据:{}, {}, {}", five_hundred, six_point_four, one);
}

数组类型
另一个包含多个值的方式是 数组( array) 。 与元组不同, 数组中的每个元素的类型必须相同。 Rust 中的数组与一些其他语言中的数组不同, 因为 Rust 中的数组是固定长度的: 一旦声明, 它们的长度不能增长或缩小。数组使用示例:

fn main() {
    let a = [1, 2, 3, 4, 5];//等价于let a: [i32; 5] = [1, 2, 3, 4, 5];
    let first = a[0]; //通过下标访问元素
    let second = a[1];
    for one in a { //遍历数组元素
        println!("{}", one)
    }
}

字符串
字符串类型是现代编程语言的重要数据类型,C/C++语言之所以被认为比较难学习,在C/C++语言中要操作字符串会非常的复杂也可能是原因之一。Rust语言中字符串有两种表现形式,一种是基本类型,表示字符串的切片,以&str表示;另一种是可变的String类型。

fn main() {
    let s1 = "Hello,String";//定义字符串切片
    println!("{}",s1.len());//输出字符串长度
    let s2 = "Hello,String".to_string(); //定义可变字符串
    //println!("{}", s1.eq(s2));//编译报错,字符串切片和可变字符串是不同的数据类型,不能比较
    println!("{}", s1.eq(s2.as_str()));//字符串切片比较
    println!("{}", s1.to_string().eq(&s2));//可变字符串比较,记住这里&s2的写法,这个将在后续的完整中解释
}

4、函数

通过重构猜数游戏的代码,我们可以看到函数就是将一段特定功能的代码封装到函数中,这样可大大降低主流业务逻辑的代码复杂度,使得程序更加便于维护。Rust中函数定义模式代码如下:

fn 函数名称(参数1: 数据类型, 参数2: 数据类型……) -> 返回数据类型 {
    //函数体
}

如果函数定义在模块(mod)中,关键字fn前面还可以使用pub关键字表明其他模块是否有权限调用这个函数。其中:函数参数和返回数据类型根据实际场景可以省略。
除了函数定义,还需要理解函数调用,函数调用模式代码如下:

let result_value = 函数名称(参数1, 参数2……);

函数调用时,没有fn关键字。如果调用的函数有返回值,则一般用let关键字声明一个变量来保存函数调用后的结果。
函数必须先定义,后调用。

fn main() {
    let secret_num = generate_secret_num();//调用生成秘密数字的函数,将秘密函数保存到secret_num中备用
    //……
}

fn generate_secret_num() -> u32 { //定义生成秘密函数的函数,该函数返回u32类型的数据
    let mut rng = thread_rng();//生成ThreadRng struct的实例
    rng.gen_range(0..101) //调用ThreadRng实例rng的成员函数gen_range生成随机数。没有return关键字和分号;,表达式的值就是函数的返回值
}

5、程序流程控制

流程控制分为选择和两种。
if语句是当条件满足时,执行某一段代码,不满足时则不执行这段代码。例如猜数游戏中的一段代码:

if input_num < secret_num {//如果输入值小于秘密数字,则将变量result_str的值绑定为“太小了”字符串切片
    result_str = "太小了!";
} else if input_num > secret_num {//如果输入值不小于秘密数字且输入值大于秘密数字,则将变量result_str的值绑定为“太大了”字符串切片
    result_str = "太大了!";
} else {//输入值等于秘密数字(等同于:输入值不小于秘密数字且输入值不大于秘密数字)
    result_str = "恭喜您,猜对了!";
    is_equ_flag = true;
}

注意这里else if,是先进行input_num < secret_num判断,如果input_num < secret_num条件不成立,再进行input_num > secret_num条件判断。最后的else是input_num < secret_num和input_num > secret_num两个条件都不成立时,才执行,两个条件都不成立,可不就等同于input_num == secret_num了吗?对于两个数字之间的关系,不就是要么大于,要么小于,要么等于这三种情况之一吗?
Rust中没有其他大部分语言都支持的switch语句,而是采用了match语句,和switch语句差不多,但是match语句没有switch缺少break关键字而引起程序逻辑错误的坑。我们可以采用了match改写猜数游戏中的关键逻辑:

use std::cmp::Ordering;//引入数字比较结果的枚举,有三种关系:大于、小于及等于
//……
fn check_input(secret_num: u32, input_num: u32, records: &mut Vec<String>) -> bool {
    let mut is_equ_flag = false;
    let mut  result_str = "";
    match input_num.cmp(&secret_num) {
        Ordering::Less => result_str = "太小了",//input_num < secret_num条件成立时执行,没有break关键字
        Ordering::Greater => result_str = "太大了",//input_num > secret_num条件成立时执行
        Ordering::Equal => {//input_num == secret_num条件成立时执行,因为有多条语句需要执行,需要用{}包裹
            result_str = "恭喜您,猜对了!";
            is_equ_flag = true;
        }//Ordering::Equal结束
    }
    let now_time = Local::now();
    let now_str = now_time.format("%Y-%m-%d %H:%M:%S");
    let one_record = format!("时间:{},输入值:{},结果:{}", now_str, input_num, result_str);
    records.push(one_record);//6
    print_info(result_str);
    is_equ_flag
}

循环,是重复执行一段代码。例如猜数游戏中的代码:

loop {//循环开始
    print_info("请输入0到100的数字:");
    let input_num = get_input_num();
    if check_input(secret_num, input_num, &mut his_records) { //函数check_input返回值为true则执行break
        break;//中断循环,或者叫循环出口
    }//函数check_input返回false,则跳转到循环开始处继续运行
}

该循环语句等同于:

loop {//循环开始
    print_info("请输入0到100的数字:");
    let input_num = get_input_num();
    if check_input(secret_num, input_num, &mut his_records) {
        break;
    }
    continue;//函数check_input返回false,则跳转到循环开始处继续运行
}

这个continue也是一个关键字,当循环中执行到continue语句时,将跳转到循环开始处执行,而continue之后的语句则不会被执行。
for循环语句:

for i in 1..5 {
    println!("{}", i);
}

运行程序后,会输出1、2、3、4。1..5时一个序列表达式,会生成1、2、3、4这个序列。可以将序列理解成数组,每个元素的值分别是:1、2、3、4。
for循环一般用于具有固定循环次数的场景。同样在for循环中也可以使用break关键字终止循环,continue语句继续下一次循环。
while条件循环语句中的条件表达式与if中的条件表达式相同,就是条件成立时,则执行循环体中的代码,例如:

let mut number = 3;
while number != 0 {
    println!("{}!", number);
    number = number - 1;
}
println!("LIFTOFF!!!");

循环开始时,number的值是3,number != 0条件成立,所以先输出了3!,然后将3-1的值绑定number,程序跳转到while处,判断条件是否成立……,所以最后程序将输出3!、2!、1!、LIFTOFF!!!
可以将猜数游戏中的loop改写为while循环:

let mut stop_loop_flag = false;
while stop_loop_flag == false {//当终止循环标志为false,则循环
    print_info("请输入0到100的数字:");
    let input_num = get_input_num();//获取用户输入
    stop_loop_flag = check_input(secret_num, input_num, &mut his_records);//输入值与秘密数字相等,函数返回true,跳转到while语句是,条件不成立,不再循环while中的语句
}

6、模块

模块是Rust中代码组织形式,与C++的命名空间、Java的包类似,模块是Rust分割一个大型应用的方式,目的是让每个文件完成的功能单一,这样文件中的代码行数就比较少,便于开发人员在应用需求变更时能够更快速的修改相关文件。一般同一个模块的所有文件都包含在一个目录下,这个目录下必须有mod.rs文件,mod.rs中声明了所有本模块所使用的子模块及文件。猜数游戏中的history模块的mod.rs文件定义如下:

//声明本模块中包含的所有文件(与mod.rs文件同级目录下)
mod show_his_records;
mod write_record_to_file;
mod read_record_from_file;
//对其他模块公布本模块可调用的函数,这里不仅仅时函数,还可以是struct等其他元素
pub use show_his_records::show_his_records;
pub use write_record_to_file::write_record_to_file;
pub use read_record_from_file::read_record_from_file;

以上是模块的定义。在调用模块的文件中,还需要在文件头部用mod关键字和use关键字,引入需要的内容:

//main.rs文件内容
mod history;//声明使用history模块

use history::{show_his_records, write_record_to_file, read_record_from_file};//使用use关键字声明调用的history模块中的函数
fn main() {
    //……
    //调用history模块中的函数,完成对应功能
    show_his_records(&mut his_records);
    write_record_to_file(&mut his_records);
    read_record_from_file();
}

模块切分,可以从开发人员的角度来看,是大型程序的一种组织源码组织形式,目的是便于程序的维护,和后续面向对象的程序设计类似。

上一篇:Rust模块的定义
下一篇:Rust代码编译为WebAssembly模块(wasm文件)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容