Rust 语法小手册
HoshinoTented
2020-05-05 03:06:42
Rust 是一门原生的命令式语言,但同样也有着函数式的特性,还有其独特的所有权系统,免去编写者手动处理内存。
## Hello world!
学习任何一门语言都可以从 Hello world 开始,让我们来看看 Rust 的 Hello world 是什么样的吧:
```rust
fn main() {
println!("Hello, world!");
}
```
* `fn` 是函数声明的关键字。
* `main` 是函数名称,和 C 一样,Rust 同样把 main 函数作为程序的入口。
* 用 `{ ... }` 来代表函数体。
* `println!` 用 println **宏** 来输出字符串。
* `"Hello, world!"` 用双引号代表字符串,单引号代表字符。
* `;` 不作为返回值的表达式应用分号结尾
## 变量
用 `let` 声明一个**不变的**变量:
```rust
fn main() {
let i = 1; // Rust 可以自动推断类型
// i = 2; // 报错
}
```
`let` 后面接上 `mut` 代表变量是**可变的**:
```rust
fn main() {
let mut i = 1; // 可变的 i
i = 2; // 编译通过
}
```
## 函数
用 `fn` 声明函数:
```rust
fn foo() {
// do something
}
```
参数格式为:`name: Type`
```rust
// i32 相当于 C 中的 int
fn foo(i: i32, j: i32) {
// do something
}
```
返回值类型格式为:`-> Type`
```rust
fn foo(i: i32, j: i32) -> i32 {
// do something
}
```
用 `return` 关键字显式返回值,或者以函数最后一行不加分号的表达式作为返回值:
```rust
fn foo(i: i32, j: i32) -> i32 {
i + j
}
```
## 输入输出
使用 `println!` **宏** 输出字符串:
```rust
fn main() {
println!("Hello, world!");
}
```
使用 `{}` 作为占位符进行格式化:
```rust
fn main() {
println!("{}, {}!", "Hello", "world");
}
```
使用 `std::io::stdin` 函数读入一行:
```rust
use std::io::stdin;
fn main() {
let mut line = String::new(); // 用 String::new 构造一个空字符串
stdin()
.read_line(&mut line) // &mut line 是获取 line 的一个可变引用
.expect("input error"); // 处理读取输入时可能导致的错误
println!("{}", line); // 格式化字符串必须为原生字符串
}
```
用 `trim` 和 `split` 来分割字符串:
```rust
fn main() {
let mut line = String::new();
/* ... */
let mut iter = line.trim() // trim 是为了删除后尾的空格和换行符
.split(" "); // 用空格分割字符串,返回的是一个 Split 迭代器
}
```
用 `next` 来读取迭代器内容,用 `parse` 来转换为数字类型:
```rust
fn main() {
let mut iter = /* ... */
let a = iter
.next().unwrap() // 读取下一个字符串,可能为空,unwrap 函数是:当为空时抛出错误,Rust 中将抛出错误称为 panic
.parse::<i32>().unwrap(); // 解析为 i32 类型,同样用 unwrap 函数,因为解析可能出错
let b: i32 = iter.next().unwrap().parse().unwrap();
}
```
## 条件控制
if 语句:
```rust
fn main() {
let i = 1;
if i == 2 { // 不需要圆括号,但是不能省略大括号
// TODO
}
}
```
if-else 语句:
```rust
fn main() {
let i = 1;
if i == 2 {
// TODO
} else {
// TODO
}
}
```
if else-if else 语句
```rust
fn main() {
let i = 1;
if i == 2{
} else if i == 3 {
} else {
}
}
```
`if-else` 和 `if else-if else` 语句可以具有值:
```rust
fn main() {
let i = 1;
let j = if i == 1 {
2 // 这里没有分号
} else {
3
}; // 这里有分号
}
```
用 `loop` 代表无止境的循环:
```rust
fn main() {
loop {
println!("LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOP");
}
}
```
用 `while` 来进行有条件的循环:
```rust
fn main() {
let mut sum = 0;
let mut i = 1;
while i <= 100 {
sum = sum + i;
i = i + 1;
}
println!("{}", sum);
}
```
用 `for` 来对迭代器进行迭代:
```rust
fn main() {
let vec = vec![1, 2, 3]; // 参见下文,这里构造了一个可变长度数组
for i in vec.iter() { // 参见下文
println!("{}", i);
}
}
```
## 容器
使用原生数组:
```rust
fn main() {
let arr = [0; 10]; // 长度为 10 的数组,默认值为 0,这个数组的类型是 [i32; 10]
println!("{}", arr[0]); // 下标从 0 开始
}
```
使用 `Vec` 可变长度数组:
```rust
fn main() {
let mut vec = Vec::new(); // 使用 Vec::new 构造一个 0 长度的 Vec
vec.push(1); // 添加元素,并通过参数类型推断 Vec 内元素类型
// 此时 vec 类型为:Vec<i32>
vec.push(2);
println!("{}", vec[0]);
}
```
使用 `get` 函数安全地获取元素:
```rust
fn main() {
let mut vec = vec![1, 2, 3]; // vec 宏,创建一个带有 1、2、3 元素的 Vec
match vec.get(0) { // 获取下标为 0 的元素,返回值是 Option,这里对 Option 进行了模式匹配
None => { // None
println!("No such element");
}
Some(v) => {
println!("{}", v);
}
}
}
```
什么是模式匹配?你可以当做把它们按照构造的样子拆开来,比如:
```rust
fn main() {
let opt = Some(1); // 用 Some(1) 构造
match opt {
None => {} // 如果 opt 是按照 None 模式构造的,则运行代码块
Some(v) => {} // 如果 opt 是按照 Some(v) 模式构造的,则运行代码块
}
}
```
有的时候你可能只需要一个模式,那么你可以用 `if-let`:
```rust
fn main() {
let vec = vec![1, 2, 3];
if let Some(i) = vec.get(1) {
println!("{}", i);
}
}
```
类似的,也有 `while-let`:
```rust
fn main() {
let vec = vec![1, 2, 3];
let mut iter = vec.iter(); // 使用 iter 函数获得 vec 的迭代器
while let Some(elem) = iter.next() { // 使用 next 获得下一个元素,类型是 Option<&i32>,因为可能没有下一个元素
println!("{}", elem);
}
}
```
## 自定义结构体
用 `struct` 关键字定义一个结构体:
```rust
struct A {
i: i32,
s: String
}
fn main() {
let a = A { // 构造结构体
i: 123,
s: "456".to_string() // "456" 是一个 &str,不是 String,使用 .to_string 转化为 String
};
}
```
使用 `enum` 定义一个枚举结构体:
```rust
enum B {
B0(i32),
B1 { i: i32, s: String }
}
fn main() {
let b0 = B::B0(1);
let b1 = B::B1 { i: 2, s: String::from("3") }; // 也可以用 String::from 函数将 &str 转化为 String
}
```
可以对结构体和枚举结构体进行模式匹配:
```rust
enum B {
B0(i32),
B1 { i: i32, s: String }
}
fn main() {
let b0 = B::B0(1);
let b1 = B::B1 { i: 2, s: String::from("3") };
match b0 {
B::B0(i) => { /* ... */ } // 匹配 B0
B::B1 { i /* 使用原本的名称 */, s: string /* 对 s 进行重命名 */ } => {} // 匹配 B1
}
}
```
## 浅谈所有权
Rust 吸引人的地方之一是它的所有权系统,所有权系统可以巧妙地自动释放内存,那什么是所有权呢?看看这段代码:
```rust
fn main() {
let s0 = String::new();
let s1 = s0;
println!("{}", s0);
}
```
猜猜看,能不能编译成功呢?
```plain
error[E0382]: borrow of moved value: `s0`
--> src\bin\test.rs:5:20
|
2 | let s0 = String::new();
| -- move occurs because `s0` has type `std::string::String`, which does not implement the `Copy` trait
3 | let s1 = s0;
| -- value moved here
4 |
5 | println!("{}", s0);
| ^^ value borrowed here after move
```
并不能,因为 `let s1 = s0` 代表着,把 s0 对 String 的所有权交给了 s1。因此再想进行 println! 操作的话,s0 一无所有,没有什么可以输出的了。
但你可以把 s0 的 String 借给 s1:
```rust
fn main() {
let s0 = String::from("123");
let s1 = &s0; // 借给 s1
println!("{}", s0);
println!("{}", s1);
}
```
模式匹配也会进行所有权转移:
```rust
fn main() {
let opt = Some(String::from("123"));
match opt {
None => {}
Some(v) => {}
}
println!("{:?}", opt); // 编译出错
}
```
有些读者可能尝试了这样的代码:
```rust
fn main() {
let opt = Some(1);
match opt {
None => {}
Some(v) => {}
}
println!("{:?}", opt);
}
```
顺利通过了编译,这是为什么呢?
因为 i32 类型实现了 Copy trait,但 trait 不在本文的讨论范围内,这里简单讲解一下:
实现了 Copy trait 的类型,会在转移所有权的地方进行自动复制,比如:
```rust
fn main() {
let a = 1;
let b = a; // i32 实现了 Copy trait,这里并没有进行所有权转移,而是复制
println!("{}", a);
println!("{}", b);
}
```
但换做 String 类型则会出错,因为 String 类型没有实现 Copy trait。
## 结尾
Rust 是一门很有趣的语言。
嗯,就这样。