人麻了,
Rust
的语法有点奇特。。。。。本文参考官方教程:https://kaisery.github.io/trpl-zh-cn/title-page.html
Rust语言从入门到入土 2:如此奇特的语法
上次介绍了Rust
的基础工具,这次首先通过一个程序介绍Rust
的基础输入输出等内容,然后再介绍Rust
的语法。
1. Hello World
编程语言入门第一课,必须得是hello world
程序。我们先来看看Rust
的hello world
是什么样子
cargo new hello_world
code hello_world
然后在src/main.rs
中编写如下的代码:
// main.rs
fn main() {
let s = "Hello World!\n";
println!("{s}");
}
然后编译、运行该项目
cargo run
A. 分析程序
我们接下来分析一下这个程序
1) 文件名:main.rs
一般Rust
源代码的后缀名使用.rs
表示,Cargo
项目中源码都放在src
目录下。源码一定要注意使用utf8
编码。
2) 注释:// main.rs
第一行是注释语句,Rust
的注释是C
语言系列风格的,行注释采用//
开头,块注释使用/*
和*/
包围。
此外,类似Python
的docstring
,Rust
它还支持更高级的文档注释,即对文档、函数等等对象进行注释,后文中将详细展开说明。
3) 函数:fn
fn
是一个关键字
(keyword
),函数定义必须以这个关键字开头。fn
是单词function
的缩写,在Rust
中,设计者比较偏向使用单词缩写,即使是关键字也不例外。在代码风格上, 某些读者可能开始会有点不习惯。但总体而言,这只是个审美偏好而已,不必过于纠结,习惯就好。
函数体使用大括号来包含。
如果我们要定义的函数有参数和返回值,可以使用以下语法(参数列表使用逗号分开,冒号后面是类型,返回值类型使用->
符号分隔):
fn Foo(input1 : i32, input2 : u32) -> i32 {
...
}
4) 主函数:main
默认情况下,main
函数是可执行程序的入口点,它是一个无参数,无返回值的函数
5) 局部变量:let
局部变量声明使用let
关键字开头,用双引号包含起来的部分是字符串常量。Rust
是静态强类型语言,所有的变量都有严格的编译期语法检查。关于Rust
的变量和类型系统将在后文详细说明。
6) 语句:let s = "Hello World!\n";
Rust
中每条语句使用分号结尾。- 语句块使用大括号。
- 空格、换行和缩进不是语法规则的一部分。
这都是明显的C语言系列的风格。
7) 标准输出:println!
Rust
最简单的标准输出是使用println!
宏来完成。请大家一定注意println!
后面的感叹号,它代表这是一个宏,而不是一个函数。
Rust中的宏与C/C++中的宏是完全不一样的东西。简单点说,可以把它理解为一种安全版的编译期语法扩展。这里之所以使用宏,而不是函数,是因为
标准输出宏可以完成编译期格式检查,更加安全。
从这个小程序的惊鸿一瞥中,大家可以看到,
Rust
的语法主要还是C
系列的语法风格。对于熟悉C
/C++
/Java
/C#
/PHP
/JavaScript
等语言的读者来说,会看到许多熟悉的身影。
B. create
和mod
2. 猜数游戏
本章将介绍Rust
中一些常用概念,并通过真实的程序来展示如何运用它们。具体包括:
let
match
方法
(method
)关联函数
(associated function
)- 外部
crate
这里是简单的介绍,后续章节会深入探讨这些概念的细节
A. 程序功能
猜数游戏是这么工作的:
- 程序将会随机生成一个 1 到 100 之间的随机整数
- 接着程序会请玩家猜一个数并输入
- 程序接着会:
- 如果玩家猜对了,它会打印祝贺信息并退出
- 如果玩家猜错了,他会提示猜测是大了还是小了
B. 创建项目
前面我们说到,尽量使用Cargo
来管理Rust
项目。所以这里我们直接使用Cargo
来创建项目
cargo new guessing_game
cd guessing_game
code ./
然后就会打开VSCode
。
接着编译、运行一下项目(我这里就在VSCode
的终端了)
cargo run
C. 编写代码
我们接下来就开始在src/main.rs
中编写代码了。
1) 获取用户输入
学习任何一门语言,最开始的一定都是学习这门语言的输入输出。前面的例子里我们已经看到,Rust
打印字符串的函数是println!
,这里我们介绍Rust
如何获取用户的输入
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
a) use std::io
类似于C
接受用户输入需要#include <sdtio.h>
,C++
接受用户输入需要#incldue <iostream>
,Rust
接受用户输入首先需要use std::io
Rust
的代码从逻辑上是分crate
和mod
管理的:
- 所谓
crate
大家可以理解为项目
,或者库
。每个crate
是一个完整的编译单元,它可以生成为一个lib
或者exe
可执行文件。而在crate内部,则是由mod这个概念管理的 - 所谓
mod
大家可以理解为namespace
。我们可以使用use
语句把其他模块中的内容引入到当前模块中来
这里:
io
是mod
的名字,io
这个mod
中提供了输入/输出函数。io
这个mod
是std
这个create
(库)的一部分,因此在前面加上std::
use
等价于C/C++
中的#include
,Python
中的import
。注意,默认情况下,Rust
会自动把标准库的一些内容自动导入到当前程序,这些内容被称为预导入
(Preclude
) 内容。我们需要使用的类型、函数不在预导入内容中,就必须使用use
语句显式地将其导入
b) println!
我们前面介绍了,println!
可以用于向终端打印字符串。但实际上和C/C++
的printf
是函数不同,Rust
中的println!
是一个宏
类似于C/C++
有宏,Rust
中也有宏。宏可以在编译时进行代码展开,因此可以避免在运行时进行函数调用的开销。这对于频繁调用的函数来说非常重要,因为它可以显著提高程序的性能。
而println!
就是一类非常常用的函数,因此Rust
将println!
设置为宏是为了提高Rust
程序的效率。
c) let
let
是Rust
的关键字,用于声明一个变量。例如下面我们声明了一个值为1
的变量,变量名为a
:
let a = 1;
但是注意,Rust
中默认所有的变量的值都是不可以改变的。如果我们尝试改变,在编译阶段就会报错。例如:
fn main() {
let a = 1;
a = 2;
}
最后的报错就是说我们尝试给一个变量a
赋值两次。
除了报错以外这里还有两个警告,这里的两个警告主要是因为
Rust
是一个严格的语言:
- 第一个警告是因为变量
a
从来没有被使用- 第二个警告是因为变量
a
中的值从来没有被使用过
d) mut
程序是无可避免的需要修改变量的值的。为此,Rust
提供了一个关键字mut
,使用该关键字,Rust
允许我们修改被修饰的变量的值。
例如上面的程序,变量a
使用mut
关键字修饰后,再次编译就不会报错
fn main() {
let mut a = 1;
a = 2;
}
Rust
之所以要这样设计,主要是避免了C/C++
中使用const
关键字的麻烦,因为要修饰指针不可变,或者指针指向的内容不可变。而且因为Rust
是内存安全型的语言,相比让所有变量都是可变的而后声明不可变的变量,将所有变量都是不可变的而后声明可变的变量更安全。
e) 打印变量
程序中我们无可避免的需要打印变量的值。在C/C++
中通过格式化字符串或者重载来实现。
Rust
实现打印变量的方式和Python
的format-string
类似,都是在打印的变量用{}
包裹来输出变量。
fn main() {
let a = 1;
println!("num of a is {a}");
}
除了直接将变变量放在{}
中之外,还可以把{}
当做占位符,放在后面。
fn main() {
let a = 1;
println!("num of a is {}", a);
}
f) String::new()
Rust
和C++
类似,是一种多范式编程语言,支持面向对象编程、函数式编程和命令式编程等多种编程范式。