【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

相关推荐
时代的凡人7 小时前
0208晨间笔记
笔记
今天只学一颗糖8 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
艾尔aier10 小时前
mini-shell成果展示
rust
饭碗、碗碗香12 小时前
【Python学习笔记】:Python的hashlib算法简明指南:选型、场景与示例
笔记·python·学习
Wils0nEdwards13 小时前
初中化学1
笔记
班公湖里洗过脚13 小时前
《通过例子学Rust》第10章 模块
rust
魔力军13 小时前
Rust学习Day4: 所有权、引用和切片介绍
开发语言·学习·rust
饭碗、碗碗香14 小时前
【Python学习笔记】:Python 加密算法全景指南:原理、对比与工程化选型
笔记·python·学习
Sheffi6615 小时前
Swift 所有权宏 `~Copyable` 深度解析:如何在 Swift 中实现类似 Rust 的内存安全模型?
rust·ssh·swift
对牛乱弹琴的秦始皇15 小时前
IoT MQ 连接失败的排查笔记
服务器·网络·笔记