import{_ as l,c as a,m as s,a as i,ai as e,o as n}from"./chunks/framework.BFSS5Pox.js";const rs=JSON.parse('{"title":"Rust","description":"","frontmatter":{},"headers":[],"relativePath":"note/Rust.md","filePath":"note/Rust.md","lastUpdated":1712396183000}'),p={name:"note/Rust.md"},t=e(`

Rust

Rust 环境搭建

rustup 是一个用于管理 Rust 版本和相关工具的命令行工具。

Unix 系统:

sh
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

Windows 系统:

下载并安装 rustup-init.exe.

安装完毕后,在命令行执行:

sh
rustc --version

可以看到输出的版本号信息,则 rust 已安装完毕。

Hello, Rust!

sh
mkdir hello_rust

创建并编辑第一个 Rust 程序:

rs
// main.rs
fn main() {
    println!("Hello, Rust!");
}

执行 rustc ./hello_rust/main.rs

可以看到在代码同目录下输出了二进制文件 main,在命令行中执行:

sh
./main

可以看到输出:

sh
> Hello, Rust!

下面我们分析一下这个程序:

在 Rust 中,函数名为 main 的函数是一个特殊的函数,它总是会被最先执行:

rs
fn main() {

}

在函数体中的代码:

rs
    println!("Hello, Rust!")

println! 是一个 Rust 宏(macro),它与函数调用的区别是它以 ! 结尾。

"Hello, Rust!" 是一个字符串,传递给了 println! 宏。

Rust 程序的编译和运行是独立进行的,这意味着你可以将编译产物直接发送给别人,别人不需要安装 Rust 也可以运行

这与 Ruby Python JavaScript 这类动态语言不同,Rust 是一门预编译静态语言(ahead-of-time compiled)

简单项目可以使用 rustc,但随着项目复杂度增长,我们可以使用 cargo 来管理项目中的三方依赖、管理真实世界中 Rust 程序开发的方方面面。

Cargo

sh
# 初始化一个 Cargo 项目
cargo new hello_cargo

执行 cargo new 后会自动帮你初始化一个 Git 仓库,如果你是在一个现存的 Git 仓库中执行的初始化,那么就不会执行此操作。

除了帮你创建了一个 HelloWorld 代码,cargo 还创建了一个 cargo.toml 文件:

toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[package][dependencies] 分别代表是一个片段:

Cargo 期望所有的源代码都存放在 src/ 目录下,项目根目录中保存如 README、LICENSE 这类的文件。

在 Cargo 中构建和运行项目

执行 cargo build 可以构建项目:

sh
cargo build

构建产物将输出在 target/debug/ 目录下,这是因为 cargo build 是调试构建(debug build)

执行:

sh
cargo run

即可运行刚刚 build 输出的产物。如果你在 cargo run 之前未构建或修改了代码,cargo 会自动帮你完成 re-build 并执行代码。

sh
cargo check

这个命令可以帮你完成代码的静态检查且不输出任何文件,由于它不需要准备输出构建产物,所以它比 cargo build 要快得多。

发布构建

与调试构建不同,可以执行:

sh
cargo build --release

来构建一个用于生产环境的产物,这会在 target/release/ 下输出产物而不是 target/debug/ 下。

发布构建的产物往往有针对生产的更多优化,同时构建需要花费的时间也更长,这也是为什么要有调试构建与发布构建的区分:调试构建用于开发时更快的看到最终效果,需要经常快速地执行构建,而发布构建则是为了最终用户使用时构建的。

Gussing Game

写一个猜数游戏:

使用 use 标识符来从标准库中引入 io 库,之后就可以在当前作用域中通过 io:stdin() 读取到用户输入:

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

执行 cargo run 后测试一下:

sh
Guess the number!
Please input your guess.
6
You guessed: 6

使用变量保存数据

在 Rust 中,使用 letconst 声明的变量默认都是不可变的,通过给 let 声明的变量加上 mut 标记,来让 guess 这个变量可变(mutatiable)

::new 中的 :: 表明:newString 类型的一个关联函数,在一些语言中它被称之为静态方法(static function)

总的来说,let mut guess = String::new(); 这一行创建了一个可变变量,当前它绑定到一个新的 String 空实例上。

读取用户输入

如果在程序开头我们没有使用 use 来引入 io 库,在代码中我们也可以这样写:

rs
std::io::stdin()

来动态地引入 io 库,通过 read_line 来从标准输入句柄获取用户输入。

&mut guess 传递给 read_line,其中 & 表明传递的是一个变量的引用,同时由于变量是不可变的,&mut 表示这个引用可以修改。

使用 Result 类型处理潜在错误

前文中我们说 read_line 会持续地将用户输入附加到传递给它的字符串中,它也会返回一个 Result 类型的值。

Result 类型是一个枚举类型,包含两种成员类型:

这些 Result 类型的作用是处理错误信息,Result 的实例具有 expect 方法,如果 io:Result 实例的值是 Errexpect 会导致程序崩溃,并显示错误信息。

如果 read_line 返回 Err,则可能是来源于底层操作系统错误的结果。如果 Result 实例的值是 Okexpect 会获取 Ok 中的值并原样返回。

在此例子中,这个值是用户输入到标准输入中的字节数。

使用 println! 占位符打印值

下面这两种 println! 是等价的,他们都可以将变量打印到指定位置:

rs
println!("You guessed: {guess}");
println!("You guessed: {}", guess);

生成一个随机数

在 Rust 标准库中不包含随机数功能,我们可以使用 rand crate:

sh
cargo add rand

安装后,我们到 Cargo.toml 中可以看到:

toml
[dependencies]
rand = "0.8.5"

这里的 "0.8.5" 实际上是 "^0.8.5" 的简写,它表示至少是 0.8.5 但小于 0.9.0 的版本。

具体可以参看语义化版本(Semantic Versioning)

Cargo 通过 Cargo.lock 文件来保证每一次构建都是可以被重现的,任何人在任何时候重新构建代码都会产生相同的结果,Cargo 只会使用你指定的依赖版本。

如果 rand 库下周发布了 0.8.6 版本,新版本中修复了一个 BUG 但存在破坏性变更,如果你没有显式地在 Cargo.toml 中升级 rand 库,那 Cargo 会按照上一次构建成功时生成的 Cargo.lock 记录的第三方库版本来构建。

如果你确实要升级 crate,可以使用:

sh
cargo update

来忽略 Cargo.lock 文件,并计算所有符合 Cargo.toml 声明的最新版本。

安装完了 rand crate,我们下面来生成一个随机数:

rs
use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_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}");
}

use rand:Rng; 其中,Rng 是一个 trait,它定义了随机数生成器实现的方法的话,此 trait 必须在作用域中。

我们调用 rand::thread_rng 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,从操作系统获取 seed。

随后调用随机数生成器的 gen_range 方法,它由 Rng trait 定义,获取一个范围表达式(Range expression)作为参数,并生成一个在此范围之间的随机数。

范围表达式使用 start..=end 这样的形式,如 1..=100 就代表 1 到 100 之间。

INFO

你不可能凭空知道应当 use 哪个 trait,以及应当从 crate 中调用哪个方法,因此每个 crate 都有说明文档。 通过调用 cargo doc --open 来构建所有本地依赖提供的文档并在浏览器中打开。

对比两个数字

rs
// 此代码不可运行
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    // --snip--

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

我们引入 std::cmp::Ordering 到作用域中。Ordering 也是一个枚举,其成员包含 LessGreaterEqual,这是在两个值进行比较时可能出现的三种结果。

cmp 方法用于比较两个值,并且可以在任何可比较的值上调用。它获取一个被比较值的引用:将 guesssecret_number 作比较。然后返回一个通过 use 引入作用域的 Ordering 枚举的成员。

使用 match 表达式,根据对 guesssecret_number 调用 cmp 返回的 Ordering 成员,来决定下一步应该要做什么。

match 表达式由众多的分支(arms)构成,每个分支都包含一个 pattern 以及 pattern 被匹配时要执行的代码。

尝试执行此代码,编译器会抛出错误:不匹配的类型(mismatched types)。Rust 有一个静态强类型系统,同时也有类型推断。

当我们写出 let guess = String::new(); 时,Rust 会帮我们推断出 guess 变量应当是 String 类型。

secret_number 是 1 - 100 之间的数字类型,而符合这个要求(1~100之间)的数字,在 Rust 中有下面几种:

Rust 默认使用 i32,所以 Rust 默认为 secret_number 推断出的类型是 i32,导致了字符串与数字作对比的情况。

要将 String 转化为数字类型才能与 secret_number 作比较:

rs
let guess: u32 = guess.trim().parse().expect("Please type a number!");

上面的代码将重新声明 guess 变量,这个特性叫隐藏(Shadowing),通过 guess.trim() 去除字符串头尾的空白字符(如用户输入 5 并按下空格后,在 Unix 系统中 guess 的值为 5\\n,在 Windows 系统中 guess 的值为 5\\r\\n

guess.parse() 方法会将字符串转换为其他类型,通过给 guess 显式指定类型来告诉 guess 方法转化的目标类型,这里的目标类型是 u32

同时,为了防止字符串中包含特殊字符等原因导致 parse 执行失败,这里用 expect 来对转化是否成功进行提示:

使用循环来允许多次猜测

可以使用 loop 关键字来创建一个无限循环,给用户更多机会来猜数。

rs
    println!("The secret number is: {secret_number}");

    loop {
        println!("Please input your guess.");

        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            },
        }
    }

当用户成功猜对后,会执行 break; 退出程序。

忽略非数字的猜测并继续游戏

目前的代码如果用户输入了非数字,会导致 parse 失败,进而导致程序退出,因此我们需要改写这部分的逻辑,将 expect 调用 改为 match

rs
        // --snip--

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

        // --snip--

这样当遇到错误时程序不再崩溃,而是进入到 match 的错误分支中处理错误:调用 continue; 继续循环。

总结

这一章里我们学习了使用 let 声明变量、变量隐藏、类型转化、match 处理多分支任务、loop 循环。

还学习了外部 crate 的使用、如何指定数据类型等

常见编程概念

变量和可变性

变量默认是不可变的(Immutable),这是 Rust 提供的众多特性之一:

下面的代码由于修改了 x 导致编译不通过:

rs
fn main() {
    let x = 5;
    println!("x is {}", x);
    x = 6;
    println!("x is {}", x);
}

编译器抛出的错误信息:cannot assign twice to immutable variable x

要让 x 变得可变,可以在声明 let 后添加 mut

rs
let mut x = 5;

这样就可以修改 x 的值了。

除了 let,还可以通过 const 声明一个常量:

rs
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

这里的 60 * 60 * 3 会在编译器编译时执行运算,这使我们可以选择更容易理解和验证的方式来写出这个值,而不是直接将常量设置为 10,800

在前文中我们介绍了变量隐藏,后声明的同名变量将会屏蔽掉之前声明的变量,直到新的变量也被隐藏或作用域结束:

rs
fn main() {
    let x = 5; // 5

    let x = x + 1; // 6

    {
        let x = x * 2;
        println!("x is {}", x); //  12
    }

    println!("x is {}", x); // 6
}

需要注意的是,隐藏与将变量标记为 mut 是有区别的,当对变量进行重新赋值时,如果没有使用 mut 那么会导致编译时错误。通过变量隐藏,我们可以用新的变量进行一些计算,但计算完之后变量依然是不可变的。

mut 与隐藏的另一个区别是:当再次使用 let 声明变量时,隐藏实际上创建了一个新的变量,我们可以改变值的类型,只不过复用这个名字:

如果没有变量隐藏,代码可能会像这样:

rs
let spaces_str = "    ";
let spaces_num = spaces_str.len();

利用变量隐藏,我们可以简单地复用相同变量名:

rs
let spaces = "    "; // 文本之间的空格数量
let spaces = spaces.len(); // 多少个空格

然而,如果使用 mut,他不允许修改变量的类型:

rs
let spaces = "    ";
spaces = spaces.len(); // 错误:不能改变变量的类型

数据类型

在 Rust 中,每一个值都属于某一种数据类型(data type),这告诉 Rust 它被指定为何种数据。

Rust 是静态类型(statically typed)语言编译时必须知道所有变量的类型,当多种类型均有可能时,例如使用 parseString 转换为数字时,必须增加类型注解,像这样:

rs
let guess: u32 = "42".parse().expect("Not a number!")

在 Rust 中有两种数据类型子集:标量和复合

标量类型(scalar)

整型

整型是一个没有小数部分的数字,例如 u32 代表一个占据 32 bit 的无符号整数。其中有符号无符号代表数字能否为负值。

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
`,155),h={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},k={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.566ex"},xmlns:"http://www.w3.org/2000/svg",width:"7.844ex",height:"2.452ex",role:"img",focusable:"false",viewBox:"0 -833.9 3466.9 1083.9","aria-hidden":"true"},r=e('',1),d=[r],c=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("mo",null,"−"),s("mo",{stretchy:"false"},"("),s("msup",null,[s("mn",null,"2"),s("mrow",{"data-mjx-texclass":"ORD"},[s("mi",null,"n"),s("mo",null,"−"),s("mn",null,"1")])]),s("mo",{stretchy:"false"},")")])],-1),o={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},E={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.186ex"},xmlns:"http://www.w3.org/2000/svg",width:"8.22ex",height:"2.072ex",role:"img",focusable:"false",viewBox:"0 -833.9 3633.4 915.9","aria-hidden":"true"},g=e('',1),u=[g],y=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("msup",null,[s("mn",null,"2"),s("mrow",{"data-mjx-texclass":"ORD"},[s("mi",null,"n"),s("mo",null,"−"),s("mn",null,"1")])]),s("mo",null,"−"),s("mn",null,"1")])],-1),b={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},m={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.025ex"},xmlns:"http://www.w3.org/2000/svg",width:"1.357ex",height:"1.025ex",role:"img",focusable:"false",viewBox:"0 -442 600 453","aria-hidden":"true"},F=s("g",{stroke:"currentColor",fill:"currentColor","stroke-width":"0",transform:"scale(1,-1)"},[s("g",{"data-mml-node":"math"},[s("g",{"data-mml-node":"mi"},[s("path",{"data-c":"1D45B",d:"M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z",style:{"stroke-width":"3"}})])])],-1),C=[F],v=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("mi",null,"n")])],-1),T=s("code",null,"i8",-1),B={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},A={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.566ex"},xmlns:"http://www.w3.org/2000/svg",width:"5.639ex",height:"2.468ex",role:"img",focusable:"false",viewBox:"0 -841 2492.6 1091","aria-hidden":"true"},D=e('',1),Q=[D],x=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("mo",null,"−"),s("mo",{stretchy:"false"},"("),s("msup",null,[s("mn",null,"2"),s("mn",null,"7")]),s("mo",{stretchy:"false"},")")])],-1),w={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},_={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.186ex"},xmlns:"http://www.w3.org/2000/svg",width:"6.016ex",height:"2.088ex",role:"img",focusable:"false",viewBox:"0 -841 2659 923","aria-hidden":"true"},q=e('',1),f=[q],H=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("msup",null,[s("mn",null,"2"),s("mn",null,"7")]),s("mo",null,"−"),s("mn",null,"1")])],-1),V={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},M={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.05ex"},xmlns:"http://www.w3.org/2000/svg",width:"1.131ex",height:"1.557ex",role:"img",focusable:"false",viewBox:"0 -666 500 688","aria-hidden":"true"},R=s("g",{stroke:"currentColor",fill:"currentColor","stroke-width":"0",transform:"scale(1,-1)"},[s("g",{"data-mml-node":"math"},[s("g",{"data-mml-node":"mn"},[s("path",{"data-c":"30",d:"M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z",style:{"stroke-width":"3"}})])])],-1),L=[R],P=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("mn",null,"0")])],-1),S={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},Z={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.186ex"},xmlns:"http://www.w3.org/2000/svg",width:"6.176ex",height:"1.714ex",role:"img",focusable:"false",viewBox:"0 -675.5 2729.7 757.5","aria-hidden":"true"},j=e('',1),O=[j],I=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("msup",null,[s("mn",null,"2"),s("mi",null,"n")]),s("mo",null,"−"),s("mn",null,"1")])],-1),G=s("code",null,"u8",-1),N={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},z={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.05ex"},xmlns:"http://www.w3.org/2000/svg",width:"1.131ex",height:"1.557ex",role:"img",focusable:"false",viewBox:"0 -666 500 688","aria-hidden":"true"},J=s("g",{stroke:"currentColor",fill:"currentColor","stroke-width":"0",transform:"scale(1,-1)"},[s("g",{"data-mml-node":"math"},[s("g",{"data-mml-node":"mn"},[s("path",{"data-c":"30",d:"M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z",style:{"stroke-width":"3"}})])])],-1),Y=[J],U=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("mn",null,"0")])],-1),$={class:"MathJax",jax:"SVG",style:{direction:"ltr",position:"relative"}},W={style:{overflow:"visible","min-height":"1px","min-width":"1px","vertical-align":"-0.186ex"},xmlns:"http://www.w3.org/2000/svg",width:"6.016ex",height:"2.072ex",role:"img",focusable:"false",viewBox:"0 -833.9 2659 915.9","aria-hidden":"true"},X=e('',1),K=[X],ss=s("mjx-assistive-mml",{unselectable:"on",display:"inline",style:{top:"0px",left:"0px",clip:"rect(1px, 1px, 1px, 1px)","-webkit-touch-callout":"none","-webkit-user-select":"none","-khtml-user-select":"none","-moz-user-select":"none","-ms-user-select":"none","user-select":"none",position:"absolute",padding:"1px 0px 0px 0px",border:"0px",display:"block",width:"auto",overflow:"hidden"}},[s("math",{xmlns:"http://www.w3.org/1998/Math/MathML"},[s("msup",null,[s("mn",null,"2"),s("mn",null,"8")]),s("mo",null,"−"),s("mn",null,"1")])],-1),is=e(`

另外,isizeusize 类型依赖运行程序的计算机架构:在 64 位架构上,它们是 64 位的,在 32 位架构上,它们是 32 位的。分别等价于 i64 i32u64u32

除了通过类型指定变量的整型类型,还可以以后缀形式使用类型,例如 let x = 57u8;。还可以通过数字字面值来指定类型:

10001_000 等价,但后者更易读。

数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b'A'
浮点型

在 Rust 中有两个原生浮点数类型:

浮点数都是有符号的。

rs
fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
}

Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数:

rs
fn main() {
    // addition
    let sum = 5 + 10;

    // subtraction
    let difference = 95.5 - 4.3;

    // multiplication
    let product = 4 * 30;

    // division
    let quotient = 56.7 / 32.2;
    let truncated = -5 / 3; // 结果为 -1

    // remainder
    let remainder = 43 % 5;
}
布尔型
rs
let t = true;
let f: bool = false;
字符类型

Rust 的 char 类型是语言最原生的字母类型,大小为四个字节:

rs
fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // 显式类型声明
    let heart_eye_cat = '😻';
}

复合类型(compound)

复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

元组类型

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。

rs
fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

可以使用模式匹配(pattern matching)来解构(destructure)元组值:

rs
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("{}", y); // 6.4
}

也可以直接用 . 跟随值的索引来访问元组中的元素:

rs
fn main() {
    let tup = (500, 6.4, 1);
    
    let x = tup.0;
    let y = tup.1;
    let z = tup.2;

    println!("{}", x); // 500
}

不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。

数组类型

与元组不同,数组中每个元素的类型必须相同。

Rust 中的数组长度是固定的。

rs
fn main() {
    let a = [1, 2, 3, 4, 5];
}

当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。

但是数组并不如 vector 类型灵活。

vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector

然而,当你确定元素个数不会改变时,数组会更有用

例如,当你在一个程序中使用月份名字时,你更应趋向于使用数组而不是 vector,因为你确定只会有 12 个元素。

rs
let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];

声明数组时,可以像这样编写数组的类型,既能约束数组中元素的类型,还能限制数组的长度:

rs
let a: [i32; 5] = [1, 2, 3, 4, 5];

这里的 i32 代表每个元素的类型,分号之后的 5 代表数组的长度为 5,包含五个元素。

还可以在类型声明中指定初始值:

rs
let b: [3, 5];

这样变量 b 就是一个长度为 5,初始值全为 3 的数组。

数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素,像这样:

rs
let c = [1, 2, 3, 4, 5];

let x = c[0]; // 1
let y = c[1]; // 2

通过索引从数组中取值的操作如果是在运行时进行的,那么代码可以顺利通过编译,但在运行时会出错:

下面这段代码可以正常通过编译,当你输入 0 1 2 3 4 访问数组时工作正常,但一旦输入了超过数组长度的索引如 10,就会抛出错误。

rs
use std::io;

fn main() {
    let a = [1, 2, 3, 4, 5];

    println!("Please enter an array index.");

    let mut index = String::new();

    io::stdin()
        .read_line(&mut index)
        .expect("Failed to read line");

    let index: usize = index
        .trim()
        .parse()
        .expect("Index entered was not a number");

    let element = a[index];

    println!("The value of the element at index {index} is: {element}");
}
sh
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
note: run with \`RUST_BACKTRACE=1\` environment variable to display a backtrace

函数

rs
fn main() {
    print_labeled_measurement(188, 's');

    let f: i32 = five();
    println!("five: {}", f);
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

fn five() -> i32 {
    // 结尾不包含分号 隐式返回表达式
    5
}

控制流

if-else & else-if

rs
fn main() {
    let number = 3;

    if number < 5 {
        println!("Yes.");
    } else {
        println!("No.");
    }
}
rs
// 不允许隐式转换 条件表达式必须返回一个布尔值
if number {
    println!("Yes.");
}

if number != 0 {
    println!("Yes.");
}

if 可以返回一个值,因此可以在 let 语句中使用 if

rs
fn main() {
    let condition = true;
    // if 与 else 分支的结果都为 i32
    let number = if condition { 5 } else { 6 };

    println!("number: {}", number);
}

由于类型必须在编译时被确定,编译器会自动识别出不符合这一原则的 if-in-let 声明:

rs
fn main() {
    // 编译报错 因为 if 与 else 分支的结果类型不同
    let number = if condition { 5 } else { "six" }
}

循环

rs
fn main() {
    let mut count = 0;

    let result = loop {
        count += 1;

        if (count == 10) {
            break count * 2;
        }
    }

    println!("result: {}", result); // 20
}

循环标签:在多个循环之间消除歧义

如果你存在一个嵌套的循环,而 break;continue; 只会应用于此时最内层的循环,可以通过循环标签来让这些关键字应用于已标记的循环:

rs
fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

上面的代码中,第一个 break; 语句只会退出内层循环,而 break 'counting_up'; 语句将直接退出外层循环。

除了 loop,Rust 还支持通过 while 来控制循环:

rs
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

当我们要实现遍历集合中的元素时,用 for 会更方便:

rs
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("value is {}", a);
    }
}

for 亦可用于计时:

这段代码中用到了 .rev() 方法来将 range 反转

rs
fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

所有权

什么是所有权

引用与借用

Slice 类型

结构体

结构体的定义与初始化

示例

方法语法

枚举和模式匹配

枚举的定义

match 控制流结构

if let 简洁控制流

`,86);function as(ns,es,ls,ps,ts,hs){return n(),a("div",null,[t,s("p",null,[i("有符号的整型可以存储从 "),s("mjx-container",h,[(n(),a("svg",k,d)),c]),i(" 到 "),s("mjx-container",o,[(n(),a("svg",E,u)),y]),i(" 在内的数字,这里的 "),s("mjx-container",b,[(n(),a("svg",m,C)),v]),i(" 代表位数。")]),s("p",null,[i("例如 "),T,i(" 可以存储 "),s("mjx-container",B,[(n(),a("svg",A,Q)),x]),i(" 到 "),s("mjx-container",w,[(n(),a("svg",_,f)),H]),i(" 在内的数字,也就是从 -128 到 127。")]),s("p",null,[i("无符号的整型可以存储从 "),s("mjx-container",V,[(n(),a("svg",M,L)),P]),i(" 到 "),s("mjx-container",S,[(n(),a("svg",Z,O)),I]),i("的数字。")]),s("p",null,[i("所以 "),G,i(" 可以存储 "),s("mjx-container",N,[(n(),a("svg",z,Y)),U]),i(" 到 "),s("mjx-container",$,[(n(),a("svg",W,K)),ss]),i(" 的数字,也就是 0 到 255。")]),is])}const ds=l(p,[["render",as]]);export{rs as __pageData,ds as default};