Skip to content

⚠️ 本文通过说明

本文通过阅读 Rust 程序设计语言 中文版 学习总结而来。

Rust 程序设计语言

Hello 系列

Hello World

执行命令:

shell
mkdir hello_world
cd hello_world
vim main.rs

输入内容:

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

执行命令:

shell
rustc .\main.rs
.\main

将会输出 Hello, World!

Hello Cargo

执行命令:

shell
cargo new hello_cargo
cd hello_cargo

文件夹中将会存在(忽略 git 相关文件):

  • src: Rust 源代码目录
  • Cargo.toml

src 文件夹会有一个简单的输出 Hello World 的 main.rs 文件。

Cargo.toml 文件
toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

[dependencies]
  • package: 表示配置包信息
  • dependencies: 配置项目的依赖

执行命令:

shell
cargo build

这个项目会在 .\target\debug 下创建可执行文件 hello_cargo

可通过添加 --release 选项还优化编译,那么生成的二进制文件将在 .\target\release 文件夹中。

执行命令:

shell
cargo run

这个命令会构建完成后自动执行二进制文件。

执行命令:

shell
cargo check

检查代码是否可以编译,并不生成可执行文件。

rustcCargo

对于简单项目,使用 Cargo 和直接使用 rustc 相比并没有太大的优势, 但拥有多个 crate 的复杂项目,交给 Cargo 来协调构建将简单得多。

猜数字游戏

游戏说明

程序会随机生成一个 1 到 100 之间的整数。接着它会提示玩家猜一个数并输入,然后指出猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。

实现步骤

创建空项目

shell
cargo new guessing_game
cd guessing_game

修改 main.rs 文件,实现一个读取一次输入值

rust
use std::io;

fn main() {
    println!("猜数字游戏启动!");

    println!("请输入你的猜测:");

    // 创建可变变量
    // 并绑定到空的 String 实例
    let mut guess = String::new();

    // 从标准输入流中读取数据
    io::stdin()
        .read_line(&mut guess)
        .expect("读取输入失败");

    println!("你的猜测为:{}", guess);
}

执行 cargo run 将会提示输入一个数字,输入后将输出这个数字

添加 rand crate

shell
cargo add rand

会在 Cargo.toml[dependencies] 部分添加 rand crate

该 crate 用来生成随机数

修改 src/main.rs 文件,生成一个随机答案

rust
use std::io;
use rand::Rng; 

fn main() {
    println!("猜数字游戏启动!");

    // 创建答案变量,并绑定随机数
    let secret_number = rand::thread_rng().gen_range(1..101); 

    // 输出答案,不调试时注释掉
    println!("答案值:{}", secret_number); 

    println!("请输入你的猜测:");

    // 创建可变变量
    // 并绑定到空的 String 实例
    let mut guess = String::new();

    // 从标准输入流中读取数据
    io::stdin()
        .read_line(&mut guess)
        .expect("读取输入失败");

    println!("你的猜测为:{}", guess);
}

use rand::Rng; 的作用是导入随机数相关实现方法的 trait

1..101[1, 100] 也可以写成 1..=100

执行 cargo run 将会输出一个随机数

继续修改 src/main.rs 文件,实现判断猜的数和答案的关系

rust
use rand::Rng;
use std::cmp::Ordering; 
use std::io;

fn main() {
    println!("猜数字游戏启动!");

    // 创建答案变量,并绑定随机数
    let secret_number = rand::thread_rng().gen_range(1..101);

    // 输出答案,不调试时注释掉
    println!("答案值:{}", secret_number);

    println!("请输入你的猜测:");

    // 创建可变变量
    // 并绑定到空的 String 实例
    let mut guess = String::new();

    // 从标准输入流中读取数据
    io::stdin()
        .read_line(&mut guess)
        .expect("读取输入失败");
        
    let guess: u32 = guess.trim().parse().expect("请输入一个正确的数字"); 

    println!("你的猜测为:{}", guess);
    
    match guess.cmp(&secret_number) { 
        Ordering::Less => println!("太小了"), 
        Ordering::Greater => println!("太大了"), 
        Ordering::Equal => println!("你赢了"), 
    } 
}

第 2 行导入 Ordering 枚举

第 25 行将 guess 字符串转化为 u32 类型
Rust 允许定义相同的变量名称,新的变量将会遮蔽(shadow)旧的变量

继续修改 src/main.rs 文件,实现允许循环输入

rust
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("猜数字游戏启动!");

    // 创建答案变量,并绑定随机数
    let secret_number = rand::thread_rng().gen_range(1..101);

    // 输出答案,不调试时注释掉
    println!("答案值:{}", secret_number);

    loop { 
        println!("请输入你的猜测:");

        // 创建可变变量
        // 并绑定到空的 String 实例
        let mut guess = String::new();

        // 从标准输入流中读取数据
        io::stdin()
            .read_line(&mut guess)
            .expect("读取输入失败");

        let guess: u32 = guess.trim().parse().expect("请输入一个正确的数字");

        println!("你的猜测为:{}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => println!("你赢了"), 
            Ordering::Equal => { 
                println!("你赢了"); 
                break; 
            }
        }
    } 
}

所有权

所有权(ownership)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收器(garbage collector)即可保证内存安全。

Rust 的内存管理方式是通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。

所有权规则

  • Rust 的每一个值都有一个被称为所有者(owner)的变量。
  • 值在任何时候有且只有一个所有者。
  • 当所有者(变量)离开作用域,这个值将被丢弃。
移动

对于大小固定的简单值,Rust 会将其存储在栈内存中并且实现了 Copy trait,所以如下代码两个变量皆有效。

rust
fn main() {
    let x = 5;
    let y = x;
    println!("x = {}", x);
    println!("y = {}", y);
}

实现了 Copy trait 的类型

  • 所有整数类型
  • 布尔类型
  • 所有浮点数类型
  • 字符类型
  • 元组(但是需要元组内部的类型也都实现了 Copy trait

而对于没有实现 Copy trait 的类型,将变量赋值给其它变量将会移交所有权。

rust
fn main() {
    let s1 = String::from("Hello");
    let s2 = s1;
    println!("s1 = {}", s1); // 这行代码会编译失败: value borrowed here after move
    println!("s2 = {}", s2);
}

因为 s1 的所有权通过移动交给了 s2 导致 s1 不在有效。

克隆

对于存储在堆内容的值,可通过使用 .clone() 方法进行深拷贝,重新创建一个变量,而不是移交所有权。

rust
fn main() {
    let s1 = String::from("Hello");
    let s2 = s1.clone();
    println!("s1 = {}", s1);
    println!("s2 = {}", s2);
}

上面的 移动克隆 对函数调用也同样有效,没有实现 Copy trait 的类型,调用函数后也会移交所有权,从而导致调用函数后无法在使用

Rust 支持通过返回值移动所有权
rust
fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("'{}' 的长度为 {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();

    (s, length)
}

借用和引用

上面通过函数移动授权,需要重新 let 一个字符串变量,有点麻烦, Rust 通过借用和引用来解决这个问题,通过将值的借给调用的函数,函数只有拥有这个值的引用,可直接对值进行读取。

默认情况下,引用只能读取值,不能通过修改,就像声明变量一样,需要显示声明为要修改的可变引用。

引用 和 可变引用 示例
rust
fn main() {
    let mut s = String::form("hello");
    
    {
        let r1 = &s; // r1 的类型为 &String
        println!("r1 = {}", r1);
    }
    // 因为上面只是将 s 的值借用给了 r1,所以 s 还拥有值的所有权
    {
        let r2 = &mut s; // 因为 Rust 限制,同一作用域只能存在一个可变引用(不可变可以存在多个,但是存在可变,就不能存在不可变),所以通过 {} 代码块隔离作用域。
        r2.push_str(", world!");
        println!("r2 = {}", r2);
    }
    pringln!("s = {}", s);
}

引用的规则:

  • 同一作用域可以存在多个引用,但是如果存在可变引用,那么只能存在可变引用,且只能存在一个。
  • 引用必须总是有效的,引用不能超过所有权的作用范围。

结构体

定义一个 Rectangle 结构体,并定义 area 方法计算矩形的面积,和 new 关联方法创建矩形实例。

rust
#[derive(Debug)]
struct Rectangle {
    width: i32,
    height: i32,
}

impl Rectangle {
    fn area(&self) -> i32 {
        self.width * self.height
    }
    
    fn new(width: i32, height: i32) -> Rectangle {
        Rectangle {
            width: width,
            height: height,
        }
    }
}

fn main() {
    let rect = Rectangle::new();
    
    println!("area = {}", rect.area());
    println!("rect = {:?}", rect);
}