Rust 是一门原生的命令式语言,但同样也有着函数式的特性,还有其独特的所有权系统,免去编写者手动处理内存。

Hello world!

学习任何一门语言都可以从 Hello world 开始,让我们来看看 Rust 的 Hello world 是什么样的吧:

fn main() {
    println!("Hello, world!");
}
  • fn 是函数声明的关键字。
  • main 是函数名称,和 C 一样,Rust 同样把 main 函数作为程序的入口。
  • { ... } 来代表函数体。
  • println! 用 println 来输出字符串。
  • "Hello, world!" 用双引号代表字符串,单引号代表字符。
  • ; 不作为返回值的表达式应用分号结尾

变量

let 声明一个不变的变量:

fn main() {
    let i = 1;          // Rust 可以自动推断类型
    // i = 2;           // 报错
}

let 后面接上 mut 代表变量是可变的

fn main() {
    let mut i = 1;      // 可变的 i
    i = 2;              // 编译通过
}

函数

fn 声明函数:

fn foo() {
    // do something
}

参数格式为:name: Type

// i32 相当于 C 中的 int
fn foo(i: i32, j: i32) {
    // do something
}

返回值类型格式为:-> Type

fn foo(i: i32, j: i32) -> i32 {
    // do something
}

return 关键字显式返回值,或者以函数最后一行不加分号的表达式作为返回值:

fn foo(i: i32, j: i32) -> i32 {
    i + j
}

输入输出

使用 println! 输出字符串:

fn main() {
    println!("Hello, world!");
}

使用 {} 作为占位符进行格式化:

fn main() {
    println!("{}, {}!", "Hello", "world");
}

使用 std::io::stdin 函数读入一行:

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);                   // 格式化字符串必须为原生字符串
}

trimsplit 来分割字符串:

fn main() {
    let mut line = String::new();

    /* ... */

    let mut iter = line.trim()         // trim 是为了删除后尾的空格和换行符
                       .split(" ");    // 用空格分割字符串,返回的是一个 Split 迭代器
}

next 来读取迭代器内容,用 parse 来转换为数字类型:

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 语句:

fn main() {
    let i = 1;
    if i == 2 {       // 不需要圆括号,但是不能省略大括号
        // TODO
    }
}

if-else 语句:

fn main() {
    let i = 1;

    if i == 2 {
        // TODO
    } else {
        // TODO
    }
}

if else-if else 语句

fn main() {
    let i = 1;

    if i == 2{

    } else if i == 3 {

    } else {

    }
}

if-elseif else-if else 语句可以具有值:

fn main() {
    let i = 1;
    let j = if i == 1 {
        2                   // 这里没有分号
    } else {
        3
    };                      // 这里有分号
}

loop 代表无止境的循环:

fn main() {
    loop {
        println!("LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOP");
    }
}

while 来进行有条件的循环:

fn main() {
    let mut sum = 0;
    let mut i = 1;

    while i <= 100 {
        sum = sum + i;
        i = i + 1;
    }

    println!("{}", sum);
}

for 来对迭代器进行迭代:

fn main() {
    let vec = vec![1, 2, 3];        // 参见下文,这里构造了一个可变长度数组

    for i in vec.iter() {           // 参见下文
        println!("{}", i);
    }
}

容器

使用原生数组:

fn main() {
    let arr = [0; 10];      // 长度为 10 的数组,默认值为 0,这个数组的类型是 [i32; 10]
    println!("{}", arr[0]); // 下标从 0 开始
}

使用 Vec 可变长度数组:

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 函数安全地获取元素:

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);
        }
    }
}

什么是模式匹配?你可以当做把它们按照构造的样子拆开来,比如:

fn main() {
    let opt = Some(1);      // 用 Some(1) 构造

    match opt {
        None => {}      // 如果 opt 是按照 None 模式构造的,则运行代码块
        Some(v) => {}   // 如果 opt 是按照 Some(v) 模式构造的,则运行代码块
    }
}

有的时候你可能只需要一个模式,那么你可以用 if-let

fn main() {
    let vec = vec![1, 2, 3];

    if let Some(i) = vec.get(1) {
        println!("{}", i);
    }
}

类似的,也有 while-let

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 关键字定义一个结构体:

struct A {
    i: i32,
    s: String
}

fn main() {
    let a = A {                 // 构造结构体
        i: 123,
        s: "456".to_string()    // "456" 是一个 &str,不是 String,使用 .to_string 转化为 String
    };
}

使用 enum 定义一个枚举结构体:

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
}

可以对结构体和枚举结构体进行模式匹配:

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 吸引人的地方之一是它的所有权系统,所有权系统可以巧妙地自动释放内存,那什么是所有权呢?看看这段代码:

fn main() {
    let s0 = String::new();
    let s1 = s0;

    println!("{}", s0);
}

猜猜看,能不能编译成功呢?

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:

fn main() {
    let s0 = String::from("123");
    let s1 = &s0;               // 借给 s1

    println!("{}", s0);
    println!("{}", s1);
}

模式匹配也会进行所有权转移:

fn main() {
    let opt = Some(String::from("123"));

    match opt {
        None => {}
        Some(v) => {}
    }

    println!("{:?}", opt);       // 编译出错
}

有些读者可能尝试了这样的代码:

fn main() {
    let opt = Some(1);

    match opt {
        None => {}
        Some(v) => {}
    }

    println!("{:?}", opt);
}

顺利通过了编译,这是为什么呢?

因为 i32 类型实现了 Copy trait,但 trait 不在本文的讨论范围内,这里简单讲解一下: 实现了 Copy trait 的类型,会在转移所有权的地方进行自动复制,比如:

fn main() {
    let a = 1;
    let b = a;      // i32 实现了 Copy trait,这里并没有进行所有权转移,而是复制

    println!("{}", a);
    println!("{}", b);
}

但换做 String 类型则会出错,因为 String 类型没有实现 Copy trait。

结尾

Rust 是一门很有趣的语言。 嗯,就这样。