【WEB3.0零基础转行笔记】Rust编程篇-第2讲:Rust简介

目录

[2.1 Rust介绍](#2.1 Rust介绍)

[2.2 安装Rust](#2.2 安装Rust)

[2.2.1 搭建 Rust 开发环境](#2.2.1 搭建 Rust 开发环境)

[2.2.2 在Windows操作系统上面安装rust](#2.2.2 在Windows操作系统上面安装rust)

[2.2.3 验证您的rust和cargo安装情况](#2.2.3 验证您的rust和cargo安装情况)

[2.2.4 了解 Cargo:您的 Rust 构建工具和包管理器](#2.2.4 了解 Cargo:您的 Rust 构建工具和包管理器)

[2.2.5 你的第一个 Rust 项目必备的cargo指令](#2.2.5 你的第一个 Rust 项目必备的cargo指令)

[2.2.6 快速回顾:核心cargo指令](#2.2.6 快速回顾:核心cargo指令)

[2.2.7 Rust 开发的关键考虑因素](#2.2.7 Rust 开发的关键考虑因素)

2.3:第一个Rust程序(Hello)

[2.3.1 理解你的第一个 Rust 程序:main属性和println!](#2.3.1 理解你的第一个 Rust 程序:main属性和println!)

[2.3.2 整理你的 Rust 代码:文件夹简介](#2.3.2 整理你的 Rust 代码:文件夹简介)

[2.3.3 撰写你的第一个示例:hello.rs](#2.3.3 撰写你的第一个示例:hello.rs)

2.3.4 了解 Rust 属性:#![allow(unused)]

[2.3.5 你的程序的核心:fn main()功能](#2.3.5 你的程序的核心:fn main()功能)

[2.3.6 使用 Cargo 运行示例](#2.3.6 使用 Cargo 运行示例)

[2.3.7 揭秘 Rust 宏:其强大之处println!](#2.3.7 揭秘 Rust 宏:其强大之处println!)

[2.3.8 Rust 核心概念:快速回顾](#2.3.8 Rust 核心概念:快速回顾)

2.4:Hello练习

[2.5 变量](#2.5 变量)

[2.5.1 理解 Rust 中的变量](#2.5.1 理解 Rust 中的变量)

[2.5.2 Rust 中的变量和可变性](#2.5.2 Rust 中的变量和可变性)

[2.5.3 类型推断](#2.5.3 类型推断)

[2.5.4 Shadowing(遮蔽)](#2.5.4 Shadowing(遮蔽))

[2.5.5 推理占位符](#2.5.5 推理占位符)

[2.5.6 常量](#2.5.6 常量)

[2.5.7 println!使用宏打印变量](#2.5.7 println!使用宏打印变量)

2.6:多样化练习

[2.7 测试](#2.7 测试)

[2.8 函数](#2.8 函数)

[2.8.1 Rust 函数入门](#2.8.1 Rust 函数入门)

[2.8.2 在 Rust 中声明函数](#2.8.2 在 Rust 中声明函数)

[2.8.3 掌握返回值](#2.8.3 掌握返回值)

[2.8.4 理解函数参数](#2.8.4 理解函数参数)

[2.8.5 参数类型转换](#2.8.5 参数类型转换)

[2.8.6 调用函数](#2.8.6 调用函数)

[2.8.7 运行你的 Rust 代码](#2.8.7 运行你的 Rust 代码)

[2.8.8 要点概览:Rust 函数一览](#2.8.8 要点概览:Rust 函数一览)

[2.8 函数练习](#2.8 函数练习)

2.9:测试


2.1 Rust介绍

Rust是一门以内存安全、高性能和并发控制 为核心的系统级编程语言,其独特的所有权和借用检查机制 能在编译时从根本上杜绝数据竞争和内存错误(如缓冲区溢出),无需垃圾回收即可保证运行时安全。在智能合约开发中,这些特性至关重要:Rust的高性能与极小运行时开销能显著降低链上执行成本(Gas费用) ;其编译时安全保证能有效预防重入攻击、整数溢出等常见合约漏洞 ,大幅提升代码可靠性;同时,强大的类型系统和模式匹配(如Option/Result)强制开发者处理所有异常状态,减少了逻辑缺陷。因此,Rust已成为许多主流区块链(如Solana、Polkadot、NEAR)的智能合约首选语言,为构建安全、高效且复杂的去中心化应用提供了坚实基础。

2.2 安装Rust

2.2.1 搭建 Rust 开发环境

欢迎阅读这篇 Rust 入门指南!我们的主要目标是为您提供 Rust 开发所需的基本工具。这包括安装两个关键组件:

(1)rustcRust 编译器:负责将你的 Rust 代码翻译成可执行程序。

(2)Cargo:Rust 的官方构建系统和包管理器,简化了项目管理、依赖项处理和各种开发工作流程。

在本课结束时,你将拥有一个功能齐全的 Rust 环境,并对如何使用 Cargo 管理你的项目有一个基本的了解。

2.2.2 在Windows操作系统上面安装rust

首先,我们将从 Rust 官方网站获取安装工具rust-lang.org

(1)rust-lang.org在您的网络浏览器中导航至。

(2)点击"GET STARTED"按钮。

(3)向下滚动至标题为"Rustup:Rust 安装程序和版本管理工具"的部分。Rustup 是安装 Rust 的推荐方法,因为它能够管理不同的 Rust 版本及其相关工具。

2.2.3 验证您的rust和cargo安装情况

重启终端后,需要验证 Rust 和 Cargo 是否已正确安装。

1、验证 Rust 编译器 ( rustc )

打开终端并输入以下命令:

bash 复制代码
rustc --version

如果安装成功,您应该会看到类似这样的输出(确切的版本号可能有所不同):

bash 复制代码
rustc 1.93.0 (254b59607 2026-01-19)

此输出确认 Rust 编译器(rustc)已安装且可访问,并显示已安装的版本。

货物核实接下来,使用以下命令验证 Cargo 安装:

bash 复制代码
cargo --version

您应该看到类似如下的输出:

bash 复制代码
cargo 1.93.0 (083ac5135 2025-12-15)

这证实了 Rust 的包管理器和构建工具 Cargo 也已安装并可以使用。

2.2.4 了解 Cargo:您的 Rust 构建工具和包管理器

Cargo 是 Rust 生态系统中不可或缺的工具。它有多种用途:

  • 包管理器 **:**Cargo 下载并管理项目的依赖项(外部库,在 Rust 中称为"crate")。

  • **构建系统:**它负责将你的 Rust 代码编译成二进制文件或库。

从本质上讲,Cargo简化了许多常见的开发任务,包括:

  • 使用标准目录结构初始化新的 Rust 项目。

  • 高效地编写代码。

  • 通过获取和链接来管理外部依赖项。

  • 运行测试以确保代码运行符合预期。

2.2.5 你的第一个 Rust 项目必备的cargo指令

让我们通过创建和管理一个简单的 Rust 项目来探索一些基本的 Cargo 命令。

a. 初始化新项目( cargo init

要创建一个新的 Rust 项目,请使用cargo init命令 <project_name>,后跟您想要的项目名称。例如,要创建一个名为 hello_rust 的项目:

bash 复制代码
cargo init hello_rust

此命令会创建一个名为"项目"的新目录hello_rust,并为其填充标准项目结构。进入您的新项目目录:

bash 复制代码
cd hello_rust

在该hello_rust目录下,cargo init将创建以下内容:

  • Cargo.toml这是您的 Rust 项目的清单文件。它采用 TOML(Tom's Obvious, Minimal Language)格式编写,包含有关您项目的元数据。

    • 它包括项目名称、版本和 Rust 版本等基本配置。

    • 至关重要的是,您可以在这里列出项目的依赖项。

rust 复制代码
[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024" # Or an earlier edition like 2021, 2018

[dependencies]
# Your project's dependencies will be listed here
  • src/此目录存放着您项目的源代码。

    • src/main.rs对于二进制(可执行)应用程序,这是默认的主源文件。cargo init它使用一个简单的"Hello, world!"程序创建:
rust 复制代码
fn  main () {
    praintln!("Hello, world!");
}
  • .gitignore:一个预配置的 Git 忽略文件,如果您使用 Git 进行版本控制,这将非常有用。它通常会忽略构建产物,例如target/目录。

b. 构建项目( cargo build

创建项目后,可以使用cargo build命令编译代码。导航到项目根目录(例如,`/etc/project` hello_rust),然后运行:

rust 复制代码
cargo build

此命令会编译文件夹中的所有 Rust 代码src。如果编译成功,生成的可执行文件将放置在target/debug/指定目录中(例如,target/debug/hello_rust)。

如果你的代码中存在任何语法错误或其他编译问题,程序cargo build将会失败并显示详细的错误信息,通常会指出源文件中问题的确切位置。例如,如果asdfa你的文件中存在类似这样的错误行main.rscargo build程序就会报告错误。

c. 格式化代码 ( cargo fmt )

Rust 非常注重代码风格的一致性。该命令会自动根据官方 Rust 风格指南格式化目录cargo fmt中的 Rust 代码。src

rust 复制代码
cargo fmt

这对于保持可读性和一致性非常有用,尤其是在团队合作时。

例如,如果您的src/main.rs文件缩进不一致或有多余的空行:

rust 复制代码
// Before cargo fmt (in main.rs)
fn main() {


    println!("Hello, world!"); // Possibly misaligned
        println!("Hello, world!"); // Possibly misaligned
}

运行后cargo fmt,代码将被重新格式化为:

rust 复制代码
// Before cargo fmt (in main.rs)
fn main() {
    println!("Hello, world!"); // Possibly misaligned
    println!("Hello, world!"); // Possibly misaligned
}

如果再添加一行println!("Hello, world!");cargo fmt再次运行,就能确保所有行格式正确。

d. 运行项目( cargo run

cargo run命令提供了一种便捷的方式来编译并立即执行您的项目。

rust 复制代码
cargo run

此命令首先检查您的代码是否需要重新编译(类似于cargo build)。如果需要构建,它会编译项目。然后,它会运行生成的可执行文件。对于二进制项目,它会在目录中cargo run查找文件并执行其中的函数。main.rssrcmain()

假设有一个main.rs类似这样的文件(为了演示,可能添加了一些打印语句):

rust 复制代码
fn main() {
    println!("Hello, world!");
    println!("Hello, world!");
    println!("Hello, world!");
}

在项目目录中执行命令cargo run,终端将输出以下信息:

e. 项目测试( cargo test

Rust 内置了编写和运行测试的功能。该cargo test命令会执行项目中定义的任何测试。

rust 复制代码
cargo test

使用新创建的初始化项目中cargo init,默认情况下没有任何测试。因此,运行测试cargo test通常会显示输出,表明它找到了 0 个测试并运行了它们:

随着您开发项目并添加测试功能,cargo test它将执行这些功能并报告结果。

2.2.6 快速回顾:核心cargo指令

总结一下项目管理中必不可少的基本 Cargo 命令:

(1)cargo init PROJECT_NAME:使用指定的名称初始化一个新的 Rust 项目。

(2)cargo build编译项目代码。

(3)cargo fmt:根据 Rust 风格指南格式化项目代码,使其"美观"。

(4)cargo run:编译(如有必要)然后运行您的项目二进制文件。

(5)cargo test执行为项目编写的所有测试。

2.2.7 Rust 开发的关键考虑因素

  • 安装后 重启 Shell: 请记住,使用 Rustup 安装 Rust 后,通常需要重启终端会话或加载 shell 的配置文件(例如,source $HOME/.cargo/env),才能使rustccargo命令在系统的 PATH 中可用。

  • 操作系统 特定安装:curl ... | sh命令专用于类 Unix 系统,例如 Linux 和 macOS。Windows 用户通常使用类似 npm 的安装程序rustup-init.exe,该程序可从 Rust 官方网站下载。

  • Cargo.toml - 项目中心:Cargo.toml文件是每个 Rust 项目的核心。它定义项目元数据,管理依赖项(crate),并配置不同的构建配置文件。

  • src/main.rs - 二进制入口点: 按照惯例,对于可执行(二进制)Rust 应用程序,`.`src/main.rs作为主要源文件,其中包含main函数,该函数是程序的入口点。对于库项目,入口点通常是 ` src/lib.rs.`

安装好这些工具并对 Cargo 有了基本的了解之后,你现在已经做好了开始 Rust 编程之旅的充分准备。

2.3:第一个Rust程序(Hello)

2.3.1 理解你的第一个 Rust 程序:main属性和println!

欢迎!在本课中,我们将剖析一个简单的 Rust 程序的基本组成部分,并探讨如何构建和运行本课程中的代码示例。我们将以一个基本的"Hello, world!"项目为基础,重点学习main函数、Rust 的一个重要特性------属性,以及常用的println!宏。

2.3.2 整理你的 Rust 代码:文件夹简介

之前,我们使用 Cargo 创建了一个名为 hello_rust 的 Rust 项目。该项目初始化时会创建一个包含 main.rs 文件的src目录。最初,hello_rust 文件的src/main.rs内容大致如下,旨在打印一次"Hello, world!":

rust 复制代码
// In src/main.rs (initial state)
fn main() {
    println!("Hello, world!");
}

在本课程中,我们将使用许多小而可执行的 Rust 代码片段来说明各种概念。为了避免每次修改主文件 src/main.rs,我们将利用 Cargo 能识别的一个特殊文件夹:examples文件夹。

你可以在 Rust 项目的根目录下创建一个 examples文件夹(例如:hello_rust/examples/)。任何直接放在这个 examples文件夹内、包含 fn main() 函数的 Rust 源文件(扩展名为 .rs),都会被 Cargo 自动识别为一个独立的、可编译和可运行的程序。这对于在单个项目中管理多个独立的示例演示来说非常有用。

2.3.3 撰写你的第一个示例:hello.rs

现在让我们将其付诸实践。首先,在你的 hello_rust 项目的根目录下创建一个名为 examples 的新文件夹。在这个 hello_rust/examples/ 目录内,创建一个名为 hello.rs 的新文件。现在,将你现有的 src/main.rs 文件内容复制到这个新的 examples/hello.rs 文件中。对于我们的第一个示例,我们将对其进行简化,使其只打印一次 "Hello, world!",并移除所有初始注释。

examples/hello.rs 的内容现在将是:

rust 复制代码
// In examples/hello.rs
fn main() {
    println!("Hello, world!");
}

2.3.4 了解 Rust 属性:#![allow(unused)]

在构建示例的过程中,您经常会在.rs文件的最顶部看到一行特定的代码:

rust 复制代码
#![allow(unused)]

这行代码引入了一个 Rust 概念,称为属性。属性是你提供给 Rust 编译器的元数据,用于影响其行为或提供关于你代码的信息。

让我们详细分析这个特定的属性:

  • #后跟感叹号 !和方括号 [ ] 表示这是一个内部属性。当它被放在文件开头时,它适用于整个 crate 或模块(在此例中,即整个 `hello.rs` 文件)。

  • **allow(unused)**是属性本身。它指示 Rust 编译器抑制那些通常会对未使用的变量、函数或导入产生的警告。

为什么要使用这个属性?在生产级项目中,这些未使用的警告非常有价值,它们可以帮助你识别并移除无用代码。然而,对于小而专注的教学示例,当我们试图演示某个特定功能时,这些警告有时会分散注意力或使输出混乱。通过使用 #[allow(unused)],我们可以保持示例代码更简洁,并让我们的关注点更集中。

现在,让我们将这个属性添加到 examples/hello.rs 文件的顶部:

rust 复制代码
// In examples/hello.rs
#![allow(unused)] // Attribute: metadata for the compiler

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

2.3.5 你的程序的核心:fn main()功能

fn main()这一行声明了一个名为 main 的函数。在 Rust 中,main 函数是特殊的:它充当任何可执行程序的入口点。当你运行一个编译好的 Rust 程序时,main函数中的代码就是最先执行的部分。每个 Rust 二进制可执行文件都必须包含一个 main函数。

我们可以在 examples/hello.rs 中添加一行注释来强调这一点:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]
// main() is the entry point of a Rust program

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

2.3.6 使用 Cargo 运行示例

通常,当你在项目根目录下执行 `cargo run` 命令时,Cargo 会编译并运行由 `src/main.rs` 生成的二进制文件。然而,要运行我们 `examples` 文件夹中的某个程序,我们需要明确告诉 Cargo 我们打算运行哪个示例。这可以通过使用 `--example` 标志来实现:

rust 复制代码
cargo run --example <example_name>

在这里,<example_name>对应于 examples 文件夹中示例文件的文件名,但不需要包含 .rs 扩展名。让我们来尝试一下。如果你在 hello_rust 项目中运行 cargo run(不带任何标志),它仍然会执行 src/main.rs(如果你没有修改它,它会打印三次 "Hello, world!")。

现在,执行以下命令:

rust 复制代码
cargo run --example hello

你会看到类似这样的输出:

2.3.7 揭秘 Rust 宏:其强大之处println!

让我们仔细看一下 println!("Hello, world!"); 这一行。它看起来像是一个普通的函数调用,但有一个关键语法差异:`println` 后面的感叹号 (!)。这个 ! 表示 println!不是一个函数,而是一个

有什么区别呢?Rust 中的宏是用于元编程的强大功能;它们本质上是能够生成其他代码的代码。当 Rust 编译器遇到宏调用(比如 println!)时,它会在主编译过程开始之前将宏展开为实际的 Rust 代码。

具体来说,println! 宏是一个标准的 Rust 宏,旨在将文本打印到标准输出(你的控制台)。它会处理你提供的字符串(以及任何额外的参数,我们稍后会看到)的格式化,并在末尾追加一个换行符,以便后续的输出从新的一行开始。

让我们更新 examples/hello.rs,并添加解释宏的注释:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]
// main() is the entry point of a Rust program

// Macros in Rust generate code at compile time and are
// invoked with an exclamation mark (!).
fn main() {
    // println! is a macro that prints text to the console.
    println!("Hello, world!");
}

备注: 在 Rust 编程语言中, 就像是一个智能的代码生成助手,它在编译阶段(也就是程序运行之前)根据你写的简短指令,自动扩展成更复杂、正确的代码。比如,当你写 println!("你好") 时,宏会在背后生成一整套处理字符串和输出的代码,让你省去重复编写的麻烦。与普通函数不同(函数在程序运行时处理数据),宏的作用是在写代码时就帮你"复制粘贴"出更多的代码,从而让编程更简洁、更灵活,尤其适合简化重复性任务或创建自定义的语法结构。

2.3.8 Rust 核心概念:快速回顾

在本课中,我们涵盖了 Rust 的几个基础元素:

  • 属性(#! [allow(unused)]):这些为编译器提供元数据。allow(unused) 是一个属性,它告诉编译器抑制未使用代码元素的警告,这对于保持教学示例的简洁性很有用。

  • fn main ():此函数是所有 Rust 可执行程序指定的入口点。程序执行从这里开始。

  • 宏(例如 println!):通过感叹号(!)调用,宏是一种编译时代码生成形式。println! 是一个标准宏,用于将格式化的文本打印到控制台,并后跟一个换行符。

  • 运行示例:要编译并运行 examples/ 目录中的特定程序,请使用命令 cargo run --example <二进制文件名>,其中 <二进制文件名> 是不带 .rs 扩展名的文件名(例如,对于 hello.rs,文件名为 hello)。

理解这些组成部分------包含 examples 目录的项目结构、fn main() 函数的作用、诸如 #[allow(unused)] 这类属性的用途,以及像 println! 这样的宏的本质------将为我们深入学习 Rust 编程奠定坚实的基础。

2.4:Hello练习

练习1:在内部 ++main.rs++ 声明一个 main 函数并打印 "Hello Rust"

rust 复制代码
#![allow(unused)]
fn main() {
    println!("Hello, world!");
}

**测试:**执行以下命令

rust 复制代码
Cargo run

2.5 变量

2.5.1 理解 Rust 中的变量

欢迎来到 Rust 编程语言中关于变量的课程。我们将探讨 Rust 如何处理变量,包括其可变性、类型如何确定、变量遮蔽的概念、常量的使用,以及将变量值打印到控制台的各种方法。

2.5.2 Rust 中的变量和可变性

Rust 的核心安全特性之一是它对变量可变性的处理。

1、默认不可变性

Rust 中的变量默认是不可变的。这意味着一旦某个值绑定到变量名,就无法更改该值。尝试重新赋值不可变变量会导致编译时错误。

请看这个例子:

rust 复制代码
// let x = 1;
// x = 2; // Error: cannot assign twice to immutable variable `x`

如果你取消注释并编译这段代码,Rust 编译器会阻止你更改 的值x

2、通过 mut 实现可变性

要声明一个可以被改变的变量,你必须在声明时,于变量名称前显式使用 mut 关键字。让我们来看看如何使变量可变:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn main() {
    // Variables
    // - Immutable by default
    // - Use mut keyword to make it mutable
    let mut x = 1; // x is declared as mutable
    println!("The initial value of x is: {}", x);
    x += 1; // The value of x can be changed
    println!("The new value of x is: {}", x); // Now x is 2
}

在这个代码片段中,x我们使用 `@mutable` 将变量声明为可变的let mut x = 1;。然后,我们可以成功地改变它的值,例如,通过递增它。这种显式地声明可变性的方法与许多其他编程语言的关键区别在于,在其他语言中,变量通常默认是可变的。

WEB3行业案例:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn main() {
    // Solana 智能合约中的变量
    // - 默认不可变
    // - 使用 mut 关键字使其可变
    let mut account_balance = 100; // 账户余额声明为可变
    println!("初始账户余额为: {}", account_balance);
    
    // 模拟一次存款操作,改变余额值
    account_balance += 50; // 账户余额可以更改
    println!("存款后的账户余额为: {}", account_balance); // 现在余额为 150
    
    // 声明一个不可变的账户所有者地址(模拟)
    let owner_pubkey = "8x7y6z5a4b3c2d1e0f9g8h7i6j5k4l3m2n1o0p";
    println!("账户所有者地址: {}", owner_pubkey);
    
    // 尝试更改账户所有者(这将导致编译错误,因为不可变)
    // owner_pubkey = "new_address"; // 取消注释会编译失败
}

2.5.3 类型推断

Rust 是一种静态类型语言。这意味着编译器必须在编译时知道每个变量的类型。然而,Rust 也提供了强大的类型推断功能。在很多情况下,你不需要显式地声明变量的类型,因为编译器可以根据赋值和使用方式推断出变量的类型。

1、默认整数类型 当你分配一个整数值而没有指定类型时,Rust 默认使用i3232 位有符号整数。

请注意以下声明:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn main() {
    // Type inference
    let y: i32 = -1; // Explicitly typed as i32
    let z = -1; // Type is inferred by Rust as i32
    println!("y is: {}, z is: {}", y, z);
    // These two lines of code are essentially the same in terms of the resulting type for z.
}

这里,y被明确地标注为一个i32。对于z,没有指定类型,因此 Rust 推断它是,i32因为-1是一个整数字面量。

web3案例重写:

rust 复制代码
#![allow(unused)]

fn main() {
    // Solana 类型推断示例
    // 显式声明类型:交易金额(可能有负值,如退款)
    let transaction_amount: i32 = -100; // 负值表示退款或支出
    // 类型推断:账户状态代码(通常为负数表示错误)
    let error_code = -1; // 编译器自动推断为 i32 类型

    println!("交易金额: {}, 错误代码: {}", transaction_amount, error_code)
}

2.5.4 Shadowing(遮蔽)

Rust 允许你声明一个与先前变量同名的新变量,这一概念被称为"变量遮蔽"。新变量会"遮蔽"之前的变量,这意味着在该变量离开作用域或被再次遮蔽之前,后续对该变量名的任何使用都将指向新变量。

变量遮蔽与将变量标记为 mut 有本质区别:

  • 当你遮蔽一个变量时,实际上是创建了一个全新的变量。之前的变量仍然存在,但在当前作用域中无法再通过该名称访问。

  • 至关重要的是,变量遮蔽允许你更改变量的类型,而这在使用 mut 时是无法做到的。可变变量只能改变其值,但不能改变其类型。

以下是一个演示变量遮蔽(包括类型改变)的示例:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn main() {
    // Shadowing
    let x: i32 = 1;
    println!("x is: {}", x); // x is 1 (i32)

    let x: i32 = 2;    // x is shadowed. It's now a new variable also named x, with value 2 (i32)
    println!("x is now: {}", x);

    let x: bool = true; // x is shadowed again. It's now a new variable of type bool with value true
    println!("x is finally: {}", x); // x is true (bool)
}

在这段代码中,x首先是一个i32值为 的元素,然后被另一个值为 的元素1遮蔽,最后被一个值为 的元素遮蔽。

WEB3案例重写:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn main() {
    // Solana 智能合约中的变量遮蔽示例
    
    // 初始声明:账户余额
    let account_data: u64 = 1000; // 余额以 lamports 为单位
    println!("初始账户余额: {} lamports", account_data); // 1000 lamports

    // 遮蔽:更新账户余额(同类型)
    let account_data: u64 = 850; // 支付手续费后的新余额
    println!("支付手续费后余额: {} lamports", account_data); // 850 lamports

    // 再次遮蔽:将账户数据转换为不同类型 - 账户状态
    let account_data: bool = true; // 账户是否激活
    println!("账户激活状态: {}", account_data); // true
}

2.5.5 推理占位符

在某些情况下,尤其是在处理更复杂的类型或泛型编程时,Rust 编译器可能需要一个类型注解,但你可能希望 Rust 自行推断具体类型。为此,你可以使用下划线 _作为类型占位符。这相当于告诉编译器:我知道这里需要一个类型,但请你自己推断出来。

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

fn  main () {
    // 类型占位符
    let  x : _ = 1234 ; // Rust 会推断类型(本例中为 i32)
    println! ( "类型占位符为:{}" , x);
}

当类型描述冗长或从上下文中显而易见时,这非常有用,它允许编译器完成这项工作。例如1234,Rust 会推断类型i32

2.5.6 常量

常量是与名称绑定且保证不会改变的值。它们与不可变变量在几个关键方面有所不同。

  • 声明 :常量使用const关键字而不是句点来声明let

  • 类型注解:常量的类型必须始终显式注解。

  • 作用域:常量可以在任何作用域中声明,包括全局作用域(任何函数之外)。

  • 命名约定 :按照约定,常量名称写成UPPER_SNAKE_CASE(例如,MAX_POINTS)。

  • :常量的值必须是常量表达式,这意味着它必须在编译时可确定。它不能是函数调用的结果,也不能是运行时计算的任何其他值。

以下是一个常量的例子:

rust 复制代码
// In examples/hello.rs
#![allow(unused)]

// 这可以放在 main() 函数之外
// 常量
const NUM: u32 = 1 ; // NUM 是一个类型为 u32 的常量,值为 1

fn  main () {
    println! ( "常量 NUM 为:{}" , NUM);
}

常量(const)与不可变变量(let)之间的主要区别:

  • 使用 let 声明的不可变变量仍然是变量。它们的值在运行时(当 let 语句执行时)设置并存储在内存中。尽管初始化后它们不能被更改,但它们的行为类似于运行时值。

  • 使用 const 声明的常量不仅仅是不可变的;它们的值实际上会在使用它们的每个地方被内联到编译后的代码中。它们在编译时解析。你不能将 const 与 mut 一起使用。

2.5.7 println!使用宏打印变量

Rust 提供了一个功能强大的println!宏,用于将文本和变量值打印到控制台。它使用格式字符串作为第一个参数,后跟要输出的任何值。

以下是几种使用方法println!

1、基本占位符 在格式字符串中 使用空花括号{}作为占位符。要打印的变量作为后续参数传递给函数println!,并按顺序填充这些占位符。

rust 复制代码
#![allow(unused)]

fn main() {
    let x: i32 = 1;
    let name: &str = "Alice";
    println!("x is {}, and name is {}", x, name); // Output: x is 1, and name is Alice
}

WEB3案例重写:

rust 复制代码
#![allow(unused)]

fn main() {
    // Solana智能合约示例:账户数据和所有者信息
    let account_balance: u64 = 50_000_000; // 账户余额:0.05 SOL(以lamports为单位)
    let owner_address: &str = "So1ana11111111111111111111111111111111111111"; // Solana账户地址格式
    
    // 使用占位符格式打印
    println!("账户余额为 {} lamports,所有者地址为 {}", account_balance, owner_address);
}

2、内联变量名( Rust 2021+ 版本) 您可以直接将变量名放在花括号内。这可以println!提高语句的可读性,尤其是在打印大量变量时。

rust 复制代码
#![allow(unused)]

fn main() {
    let x = 1;
    let y = 10;
    // Inline
    println!("x is {x} and y is {y}"); // Output: x is 1 and y is 10
}

WEB3案例重写:

rust 复制代码
#![allow(unused)]

fn main() {
    // Solana智能合约示例:账户余额和交易金额
    let sender_balance: u64 = 100_000_000; // 发送者余额,以lamports为单位(1 SOL = 1,000,000,000 lamports)
    let transfer_amount: u64 = 5_000_000;   // 转账金额:0.005 SOL
    
    // 使用内嵌格式打印
    println!("发送者余额为 {sender_balance} lamports,转账金额为 {transfer_amount} lamports");
}

3、位置参数 你可以在花括号内使用数字(例如 {0}, {1})来按位置(从 0 开始)引用传递给 println! 的参数。这允许你在输出中重复使用参数或更改它们的顺序。

rust 复制代码
#![allow(unused)]

fn main() {
    let x = 1;
    // Positional arguments
    println!("{0} + {0} = {1}", x, x + x); // Output: 1 + 1 = 2
    println!("The second argument is {1}, the first is {0}", x, x + x); // Output: The second argument is 2, the first is 1
}

这里,{0}指的是格式字符串(x)之后的第一个参数,{1}指的是第二个参数(x + x)。

WEB3案例重写:

rust 复制代码
#![allow(unused)]

fn main() {
    // Solana智能合约示例:交易金额和账户操作
    let base_amount: u64 = 1000; // 基础金额,单位:lamports
    
    // 使用位置参数打印
    println!("{0} + {0} = {1}", base_amount, base_amount + base_amount);
    println!("第一个参数是 {0},第二个参数是 {1}", base_amount, base_amount * 3);
}

4、调试打印

对于更详细的输出,尤其是在处理复杂类型(如结构体或枚举)或进行调试时,可以使用调试格式说明符:

  • {:?}:这请求变量的调试表示形式。

  • {:#?}:这会请求一个"漂亮的"调试打印,以更易于人类阅读的方式格式化输出,通常会对复杂结构进行缩进和换行。

rust 复制代码
#![allow(unused)]

fn main() {
    let x = 1;
    let point = (10, 20); // A tuple
    
    // Debug
    println!("DEBUG simple: x is {:?}", x);    // Output: DEBUG simple: x is 1
    println!("DEBUG pretty simple: x is {:#?}", x);   // Output: DEBUG pretty simple: x is 1 (for simple types, similar to :?)

    println!("DEBUG tuple: point is {:?}", point); // Output: DEBUG tuple: point is (10, 20)
    println!("DEBUG pretty tuple: point is {:#?}", point);
    // Output for pretty tuple:
    // DEBUG pretty tuple: point is (
    //     10,
    //     20,
    // )
}

调试打印对于检查数据状态非常有用。大多数标准库类型和您定义的类型(如果您派生了它们Debug)都可以通过这种方式打印出来。

本课涵盖了 Rust 中变量的基础知识:变量的默认不可变性、mut关键字、类型推断、变量遮蔽、常量以及使用各种方法打印变量println!。这些概念是编写任何 Rust 程序的基础。

WEB3案例重写:

rust 复制代码
#![allow(unused)]

fn main() {
    // Solana智能合约示例:账户数据和元组结构
    let account_balance: u64 = 100_000_000; // 账户余额,单位:lamports
    let account_info = ("So1ana11111111111111111111111111111111111111", 50_000_000); // 元组:账户地址和余额
    
    // 使用Debug格式化打印
    println!("DEBUG简单打印: 账户余额为 {:?}", account_balance);
    println!("DEBUG美化打印: 账户余额为 {:#?}", account_balance);
    
    println!("DEBUG简单打印: 账户信息为 {:?}", account_info);
    println!("DEBUG美化打印: 账户信息为 {:#?}", account_info);
    // 美化打印输出示例:
    // DEBUG美化打印: 账户信息为 (
    //     "So1ana11111111111111111111111111111111111111",
    //     50000000,
    // )
}

2.6:多样化练习

例子: 执行以下命令运行++./solutions/examples/var.rs++

cargo run --example var

练习: 练习正在进行中++./exercises/src/main.rs++

练习1: 使其count成为可变变量

测试

cargo run

2.7 测试

2.8 函数

2.8.1 Rust 函数入门

函数是 Rust 中的基本构建模块,它允许你封装逻辑、促进代码重用并有效地组织程序结构。本课程将全面介绍 Rust 中函数的定义、调用和各种细微差别。

2.8.2 在 Rust 中声明函数

在 Rust 中,函数使用fn关键字声明函数。基本语法包括函数名、用括号括起来的参数列表以及可选的返回类型。

语法格式:

rust 复制代码
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // function body: statements and/or expressions
}

让我们通过一个简单的例子来说明。我们将创建一个名为 add 的函数,它接受两个无符号 32 位整数(u32)作为输入,并返回它们的和,同样也是一个 u32 类型。

rust 复制代码
#![allow(unused)]

// 定义一个名为 add 的函数,它接受两个 u32 类型的参数并返回它们的和
fn add(x: u32, y: u32) -> u32 {
    // 使用 return 关键字返回 x 与 y 的和
    return x + y;
}

fn main() {
    // 调用 add 函数,传入 23 和 35 作为参数,将返回值赋给变量 sum
    let sum = add(23, 35);
    // 打印 sum 的值
    println!("{sum}");
}

让我们分解这个 add 函数:

  • fn add:这声明了一个名为 add 的函数。

  • (x: u32, y: u32):这定义了两个参数:类型为 u32x,以及同样为 u32 类型的 y。在 Rust 中,参数的类型注解是必须的。

  • → u32:这指定了函数 add 将返回一个 u32 类型的值。

  • { return x + y; }:这是函数体。在这里,x + y 计算总和,而 return 关键字明确地将这个总和返回给调用者。注意 return 语句末尾的分号。

2.8.3 掌握返回值

Rust 在函数返回值方面提供了很大的灵活性。你可以使用显式return语句返回值,也可以利用 Rust 基于表达式的特性来实现隐式返回。

1、显式返回

如我们最初的函数所示addreturn关键字明确指定了要返回的值。return语句后必须跟一个';'。

为了更清晰地表达,我们将第一个版本重命名addadd_with_return

rust 复制代码
fn add_with_return(x: u32, y: u32) -> u32 {
    return x + y;
}

2、隐式返回

Rust 是一种基于表达式的语言,这意味着大多数语句都会得到一个值。如果函数体的最后一行是一个表达式(没有尾随分号),其值会自动被返回。在这种情况下,就不需要使用 return 关键字。

让我们重构 add 函数,使用隐式返回:

rust 复制代码
fn add(x: u32, y: u32) -> u32 {
    x + y // No semicolon; this expression's value is returned
}

重要提示:函数块中只有最后一个表达式才能是隐式返回。函数内任何前面的代码行都必须是语句,通常以分号结尾。

考虑以下修改后的add函数:

rust 复制代码
#![allow(unused)]

fn add(x: u32, y: u32) -> u32 {
    println!("Calculating sum for x = {}", x); // This is a statement
    println!("And y = {}", y);                 // This is also a statement
    x + y                                      // This is an expression, its value is returned
}

fn main() {
    let sum = add(23,35);
    println!("{sum}");
}

这里,println! 宏调用是语句(它们执行一个动作,但不求值为我们希望从 add 函数返回的值)。最后一行 x + y 是一个表达式,其结果成为 add 函数的返回值。

4、返回多个值

Rust 中的函数可以使用元组作为返回类型来返回多个值。元组是将不同类型的值组合在一起的集合。

让我们修改add函数,使其同时返回总和(a u32)和一个布尔标志(bool):

rust 复制代码
#![allow(unused)]

fn add_multiple(x: u32, y: u32) -> (u32, bool) {
    return (x + y, true); // Returns a tuple containing the sum and 'true'
}

fn main() {
    let (sum,bool_flag) = add_multiple(23,35);
    println!("第一个返回值:{sum},第二个返回值{bool_flag}");
}

返回类型-> (u32, bool)表示该函数将返回一个元组,其中第一个元素是 a u32,第二个元素是 a bool

备注 :Rust 中的元组是一个固定长度、可存储多个不同类型值的复合数据类型。它通过小括号 () 定义,元素之间用逗号分隔,例如 (1, "hello", true) 就是一个包含整数、字符串和布尔值的元组。元组可以像结构体一样将多个相关数据组合在一起,支持通过模式匹配解构或使用点号加索引(如 tuple.0)访问元素,常用于从函数返回多个值或临时组合数据,其长度和类型在编译时确定,提供了轻量且类型安全的多值封装能力。

4、没有显式返回值的函数(单元类型)

如果一个函数没有显式返回值,它会隐式返回"单元类型",表示为 ()。单元类型是一个空元组,实质上表示"没有有意义的返回值"。如果函数不返回任何内容,你可以在函数签名中省略 ()

以下是一个 print_message 函数的示例,它接收一个 String 并将其打印到控制台。它执行一个操作但不返回任何具体数据。

rust 复制代码
fn print_message(s: String) { // Implicitly returns ()
    // In Rust 2021 edition and later, "{s}" uses s as an implicit named argument.
    // This line prints the content of the string 's' five times, concatenated.
    println!("{s}{s}{s}{s}{s}");
}

2.8.4 理解函数参数

参数是函数的输入。它们在函数名后的括号内定义,每个参数以 name: Type 的形式指定。所有函数参数的类型标注都是必须的。

2.8.5 参数类型转换

调用函数时,传入的参数必须与函数签名中定义的参数类型匹配。有时,这需要进行显式的类型转换。

例如,我们的 print_message 函数期望一个 String 类型的参数 s。然而,Rust 中的字符串字面量(例如 "")属于 &str 类型(一个字符串切片,即对字符串数据的引用)。

要将像 "" 这样的字符串字面量传递给 print_message,我们必须将其从 &str 转换为 String。一种常见的转换方式是使用 .to_string() 方法:

rust 复制代码
// Assuming print_message function is defined as above
// print_message("🐸"); // This would cause a type error

print_message("🐸".to_string()); // Correct: "🐸" (&str) is converted to String

String(一个拥有所有权的、堆分配的字符串)与 &str(一个字符串切片)之间的区别是 Rust 所有权系统中的一个核心概念,这将在后续课程中详细探讨。

2.8.6 调用函数

函数定义完成后,可以通过函数名后跟括号来调用它。如果函数需要参数,则将参数放在括号内。

如果一个函数返回一个值,你可以使用绑定将该值赋给一个变量let

让我们看看如何在main函数内部调用我们之前定义的函数,函数是 Rust 程序的入口点。

rust 复制代码
// 此属性可放在文件顶部,用于在开发期间抑制编译器对未使用代码的警告。
#![allow(unused)]

// 定义一个名为 `add` 的函数,接收两个 `u32` 类型的参数 `x` 和 `y`,返回它们的和(同样为 `u32` 类型)。
fn add(x: u32, y: u32) -> u32 {
    // 在 Rust 中,函数最后一行的表达式(不带分号)将作为返回值,这称为"隐式返回"
    x + y
}

// 定义一个名为 `print_message` 的函数,接收一个 `String` 类型的参数 `s`,没有返回值(即返回单元类型 `()`)
fn print_message(s: String) {
    // 打印字符串 `s` 五次,使用花括号占位符来插入变量
    println!("{s}{s}{s}{s}{s}");
}

// Rust 程序的入口点,所有可执行程序都从 `main` 函数开始运行
fn main() {
    // 声明变量 `x` 并绑定值 1(类型推断为 `u32`,因为后续传递给 `add` 函数)
    let x = 1;
    // 声明变量 `y` 并绑定值 2
    let y = 2;

    // 调用 `add` 函数(该函数使用隐式返回),并将其结果存储在变量 `z` 中
    let z = add(x, y);
    // 使用位置参数打印加法结果。花括号内的数字对应后续参数的顺序(从0开始)
    println!("{} + {} = {}", x, y, z); // 预期输出: 1 + 2 = 3
    // 另一种使用位置参数的方式:明确指定索引,效果同上
    println!("{0} + {1} = {2}", x, y, z); // 预期输出: 1 + 2 = 3
    // 使用变量名直接作为占位符(Rust 1.58+ 支持),使格式字符串更清晰
    println!("{x} + {y} = {z}"); // 预期输出: 1 + 2 = 3

    // 调用 `print_message` 函数。
    // 注意:函数期望一个 `String` 类型,但字符串字面量 `"🐸"` 是 `&str` 类型,
    // 因此需要使用 `.to_string()` 方法将其转换为 `String`(堆分配的可变字符串)。
    print_message("🐸".to_string()); // 预期输出: 🐸🐸🐸🐸🐸
}

2.8.7 运行你的 Rust 代码

要编译和运行你的 Rust 代码,通常需要使用 Cargo,它是 Rust 的构建系统和包管理器。如果你的代码位于 Cargo 项目 examples 目录下名为 func.rs 的文件中,则可以在终端中通过以下命令运行:

rust 复制代码
cargo run --example func

2.8.8 要点概览:Rust 函数一览

让我们总结一下本课程涵盖的核心概念:

(1) fn 关键字:用于在 Rust 中声明所有函数。

(2)类型注解:函数参数和返回值的类型注解是必须的,它们确保了类型安全。

(3) return 关键字 :可用于显式返回。使用 return 的语句必须以分号结尾。

(4)隐式返回:如果函数的最后一行是一个表达式(没有分号),其值将自动作为返回值。

(5)分号 :区分语句(以 ; 结尾)和表达式(如果作为块或函数的返回值,通常不以 ; 结尾)。

(6) 元组:用于使函数能够返回多个值。

(7)单元类型 ():没有显式返回值的函数的隐式返回类型,它表示"没有值"。

(8)常见类型 :我们接触了 u32(无符号 32 位整数)、bool(布尔值:truefalse)、String(拥有所有权的字符串)和 &str(字符串切片)。

(9) .to_string() 方法 :一种将 &str 等类型转换为拥有所有权的 String 的常用方法。

(10) println! :一个用于向控制台打印格式化输出的多功能宏。

(11) Rust 2021 隐式命名参数 :在 println! 宏(和其他格式化宏)中,如果变量 s 在作用域内,{s} 可以作为 {s = s} 的简写形式。这就是为什么 println!("{s}{s}{s}{s}{s}"); 能有效地将变量 s 的内容打印五次。

(12) #[allow(unused)]:一个 crate 级属性,在开发过程中很有用,用于抑制编译器关于未使用的代码、变量或函数的警告。

理解函数对于编写任何有实际意义的 Rust 程序都至关重要。通过掌握函数的声明、参数处理和返回值机制,你将能够很好地构建健壮且可维护的应用程序。

2.8 函数练习

例子:执行以下命令运行++./solutions/examples/func.rs++

rust 复制代码
cargo run --example

练习:练习正在进行中++./exercises/src/lib.rs++

练习1

rust 复制代码
pub  fn  mul () {
    待办事项!();
}

mul修复内部函数++./exercises/src/lib.rs++

该函数应接受两个指定类型的输入u32,将它们相乘,然后返回乘积。

练习 2

rust 复制代码
pub  fn  div () {
    待办事项!();
}

div修复内部函数++./exercises/src/lib.rs++

该函数应接受两个类型为 的输入u32,将它们相除,然后返回商。

测试

rust 复制代码
cargo test

2.9:测试

1、在编程中,变量的主要用途是什么?

  • ❌ 定义程序用户界面的整体结构。

  • ❌ 自动将源代码编译成可执行文件。

  • ✅ 存储和管理数据,这些数据在程序执行过程中可以被引用并可能被修改。

  • ❌ 执行特定的操作系统命令。

2、如果一个编程语言中的变量被描述为"可变的",这通常意味着什么?

  • ❌ 它由垃圾回收器自动管理。

  • ✅ 在最初赋值后,它的值可以被更改。

  • ❌ 它只能存储数值数据类型。

  • ❌ 它的值是固定的,一旦设置就无法更改。

3、在学习编程时,完成动手练习的主要教学目标是什么?

  • ✅通过积极应用所学的概念来巩固理解并发展实践技能。

  • ❌ 被动观察他人如何构建解决方案。

  • ❌ 在教材本身中寻找错误或缺陷。

  • ❌ 快速构建一个大型、复杂的软件应用程序。

4、在 Rust 中,使用哪个关键字来声明一个函数?

  • def

  • function

  • fn

  • func

5、哪个陈述准确地描述了 Rust 函数中隐式返回是如何工作的?

  • ❌ 隐式返回仅适用于那些特别声明返回类型为 implicit 的函数。

  • ❌ 必须使用 return 关键字,但不能带分号。

  • ✅ 函数体中最后一个表达式的值(如果它不以分号结尾)会被自动返回。

  • ❌ 如果函数内的任何表达式后面没有分号,它都可以成为隐式返回。

6、关于 Rust 中的函数参数,以下哪个说法是正确的?

  • ❌ 类型注解是可选的,如果省略,Rust 会进行推断。

  • ❌ 如果实参类型与形参类型不匹配,Rust 会自动转换,无需显式转换。

  • ❌ 参数只能是像 i32bool 这样的基本类型。

  • ✅ 所有函数参数的类型注解都是必须的。

7、如何构造一个 Rust 函数以返回多个(可能类型不同的)值?

  • ❌ 通过返回一个标准数组。

  • ❌ 通过使用多个 return 语句,每个返回一个不同的值。

  • ✅ 通过指定一个元组作为函数的返回类型。

  • ❌ Rust 函数不可能直接返回多个不同的值。

8、在 Rust 中,如何定义一个名为 'calculate' 的函数,它接收两个无符号 32 位整数作为输入,并返回一个无符号 32 位整数?

  • def calculate(a: u32, b: u32) → u32 { /* 实现 */ }

  • ✅fn calculate(a: u32, b: u32) → u32 { /* 实现 */ }

  • calculate(a: u32, b: u32) returns u32 { /* 实现 */ }

  • function calculate(a: u32, b: u32): u32 { /* 实现 */ }

9、如果一个 Rust 函数预期返回一个无符号 32 位整数,应如何指定其返回类型?

  • ❌ : u32

  • ❌ ⇒ u32

  • ✅ → u32

  • ❌ returns u32

10、哪个 cargo 命令通常用于编译和运行 Rust 项目中的所有测试?

  • cargo check --tests

  • cargo test ✅

  • cargo run --tests

  • cargo build --test

相关推荐
齐生11 天前
iOS 知识点 - IAP 是怎样的?
笔记
tingshuo29172 天前
D006 【模板】并查集
笔记
DongLi012 天前
rustlings 学习笔记 -- exercises/06_move_semantics
rust
ssshooter2 天前
Tauri 踩坑 appLink 修改后闪退
前端·ios·rust
布列瑟农的星空2 天前
前端都能看懂的rust入门教程(二)——函数和闭包
前端·后端·rust
tingshuo29173 天前
S001 【模板】从前缀函数到KMP应用 字符串匹配 字符串周期
笔记
蚂蚁背大象3 天前
Rust 所有权系统是为了解决什么问题
后端·rust
布列瑟农的星空3 天前
前端都能看懂的rust入门教程(五)—— 所有权
rust
Java水解4 天前
Rust嵌入式开发实战——从ARM裸机编程到RTOS应用
后端·rust
Pomelo_刘金4 天前
Rust:所有权系统
rust