最近跟着学习HackQuest web3 相关的知识
这个平台的宗旨是:
FREE Web3 dev education platform Learn coding, join hackathon, build startup
HackQuest 这是一个适合所有人的 web3 编程平台。有很多 web3 相关的课程可以学习,除了课程外还有项目实战供大家去练习,检验自己的学习成果。
除了学习和实战外,这里还有一个黑客松专栏,可以通过参加黑客松赢取巨额奖池。
希望本文章可以起到抛砖引玉的作用,期待大家共同探索更广阔的领域。
为什么学习 Rust
先说说 Arbitrum Stylus
2023年8月31日,Arbitrum 开发团队 Offchain Labs推出了下一代编程环境 Arbitrum Stylus 的代码和公共测试网。 Stylus 是对 Arbitrum Nitro 技术栈的升级,该技术栈支持 Arbitrum One、Arbitrum Nova 和 Arbitrum Orbit 链。此次升级在 EVM 中增加了第二个虚拟机,EVM 合约的行为完全如同它们在以太坊中的行为一样。我们称这种范式为 EVM+。
以太坊虚拟机( Ethereum Virtual Machine,简称 EVM )是以太坊区块链的核心组件之一,负责执行以太坊网络上的所有智能合约代码。EVM 是一个完全隔离的虚拟环境,可以在全球任何一个节点上以相同的方式执行代码,确保了以太坊网络的去中心化和安全性。
这第二个虚拟机执行的是 WebAssembly( WASM )而不是 EVM 字节码。有了 WASM 虚拟机,任何能够编译成 WASM 的编程语言都在 Stylus 的覆盖范围内。这就意味着传统互联网领域的程序员们如果熟练掌握 Rust、C、C++ 等可以编译为 WASM 的编程语言,那么他们无需额外学习 Solidity 等原生智能合约开发语言,也可以参与到区块链合约开发中。
WebAssembly( WASM )是一种为网络浏览器设计的低级字节码格式,开发者可以使用 C、C++、Rust 等多种语言编写代码,然后编译成 WASM。这为不同背景的开发者提供了更多的选择,使得他们可以使用自己熟悉的语言来开发 Web 应用程序。引入 WASM 到区块链技术,意味着开发者可以使用多种编程语言来编写智能合约,而不仅仅是 Solidity。
与使用 Solidity 相比,WASM 程序效率更高。这有很多原因,包括 Rust 和 C 的编译器开发几十年的历史。WASM 还拥有比 EVM 更快的运行时,导致执行速度更快。通常,使用 WASM 语言的合约与使用 Solidity 的合约相比,性能提升了10倍。 通过推出 Stylus,Arbitrum 为其开发者社区打开了一扇大门,将原本局限于大约20,000名 Solidity 开发者的圈子,扩展至包括数百万使用 Rust 和 C 语言的开发者,大大增加了参与和创新的可能性。
猜数游戏需求
Bulls and Cows 是一款经典的猜数游戏,其中系统会生成一个随机数,玩家尝试猜测这个数字。每一次猜测后,系统会给出提示来帮助玩家逐步逼近正确答案。
Rust 中的 Cargo
- Cargo 是 Rust 编程语言的包管理器和系统构建工具。就像你建造一座大楼需要工具和材料一样,Cargo 提供了你在 Rust 项目开发中所需的所有工具和库
- Cargo 能帮助你从创建项目、下载项目依赖、编译代码、构建和测试、直至最后运行和部署你的项目,就像一个贴心的小助手,确保一切都井井有条的运行起来
准备工作
安装 Cargo
- 安装 Rust, 就会一并安装 Cargo, 无需额外再安装
- 安装Rust 或 Rust Install
如果安装成功,将出现下面这行:
Rust is installed now. Great!
OK,这样就已经完成 Rust 安装啦。
vscode
Cargo 相关指令
cargo new (project's name) // 创建一个新的 cargo 项目
cargo build // 编译项目
cargo run // 对项目进行编译,然后再运行
创建项目
在命令行中执行以下命令进行创建项目
cargo new bulls_and_cows
创建项目后,可以看到 cargo 自动生成的文件和目录:
在 Rust 中,项目名通常使用小写字母,并用下划线代替空格来保持一致性和兼容性。
管理依赖
- 根目录有一个文件:Cargo.toml,它包含项目信息和依赖的配置信息。
- [package]:记录着项目的元信息。name 项目名称,version 项目版本号,edition rust语言版本
- [dependencies]:是项目依赖的列表,指明了你的项目需要哪些外部库及其版本。
在 Cargo.toml 中,主要通过各种依赖段落来描述该项目的各种依赖项:
- 基于 Rust 官方仓库
crates.io,通过版本说明来描述 - 基于项目源代码的 git 仓库地址,通过 URL 来描述
- 基于本地项目的绝对路径或者相对路径,通过类 Unix 模式的路径来描述
这三种形式具体写法如下:
[dependencies]
rand = "0.3"
hammer = { version = "0.5.0"}
color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" }
项目流程
- 生成随机数
- 程序捕获用户输入的数字
- 判定用户输入的数字是否等于生成的随机数
安装依赖
这里我们额外引入了一个 rand 库用来生成随机数,并指定使用 0.8 版本。
直接把依赖写到 Cargo.toml 文件中,我们本地还是没有这个依赖包的,还需要在项目目录中额外执行一下 check 命令
cargo check
这个命令的作用是:
- 根据
Cargo.toml中的依赖列表下载缺失的依赖。 - 将依赖解压到本地的 Cargo 缓存目录(通常在
$HOME/.cargo/registry)。 - 编译并将二进制文件存放到
target目录中。 - 快速的检查一下代码能否编译通过。
生成随机数
我们使用 rand 库来生成随机数。
- 打开
main.rs文件, 通常位于根目录的src目录下
-
使用 use 引入库,在文件顶部添加
use rand::Rng;。 这样我们就可以在接下来的代码中直接使用rand库提供的方法了。 -
添加如下代码来生成一个 1 - 100 之间的随机数
let secret_number = rand::thread_rng().gen_range(1..100);
secret_number: 声明一个变量用于存储生成的随机数 rand: 引入的库,包含了随机数生成的功能 thread_rng: 是 rand 库中的一个函数,他返回一个随机数生成器。 gen_range: 是随机数生成器的方法,用于生成一个指定范围内的随机数。1..100 定义了一个范围,这个范围包括起始值 1,但不包括结束值 100。
- 添加尝试次数的记录 声明一个变量用来记录玩家尝试的次数,他不仅可以用来显示玩家当前的进度,还可以用来决定游戏何时结束
let mut attempts = 0;
程序捕获用户的输入
- 引入 io 库
为了读取用户在终端的输入,我们需要使用 std::io 模块。 这个模块提供了各种处理输入和输出的功能,我们将用它来读取用户输入的数字。
use std::io;
- 创建一个变量用来存储用户的输入
为了存储用户输入的数据,我们需要一个变量,这个变量是一个可变变量,因为用户可以多次输入,导致变量可以随时改变。
let mut input_data = String::new();
声明一个名为 input_data 的可变字符串变量,并初始化为 String::new() 空字符串。 String::new(): 创建一个新的空字符串,作为用户输入数据的存储容器。
- 读取用户输入
接下来,使用 sto::io 模块中的 stdin 函数来读取用户的输入。
io::stdin().read_line(&mut input_data);
这行代码使用
stdin()函数获取终端输入的句柄,并调用read_line方法,将用户输入的数据写入之前创建的input_data字符串。
借用(Borrowing) :是指通过引用来获得数据的访问权,而不是所有权,用符号&表示。借用使得可以在不转移所有权的情况下,让多个部分同时访问相同的数据。Rust 的借用分为可变借用(mutable borrowing)和不可变借用(immutable borrowing)两种形式。
- 使用
expect处理错误
read_line() 方法在读取输入时可能会遇到各种问题,例如用户输入非法字符或输入过程中发生中断。
read_line() 方法会返回一个 Result 类型的值, 这是一个枚举,其值可以是 Ok 或 Err。 Ok: 表示操作成功,包含成功读取的字节数。 Err: 表示操作失败,包含错误信息
为了处理可能发生的错误,我们使用 expect 方法。 如果 read_line 方法返回一个 Err 值, expect 将会停止程序并显示你提供的错误信息,他可以帮你快速发现并处理错误。
io::stdin().read_line(&mut guess).expect("Something goes wraong")
这样,如果读取输入时出现错误,程序会停止运行, 并显示 "Something goes wraong"
- 设置循环竞猜
现在我们需要考虑一个新的问题,就是一个游戏中,通常玩家不会一次就猜中正确的答案,因此我们需要提供多次猜测的机会。
在 Rust 中,我们可以使用 loop 关键字来创建一个无限循环,这样玩家就可以进行多次尝试
loop {
prinln!("Please input a number:");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Something goes wrong");
}
- 去除空白字符
当用户输入数据时,他们可能会在开始或结束时不小心加入空格或换行符。这些额外的空白符会干扰程序的判断逻辑。我们可以使用 trim 方法来清除这些不需要的字符。
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Something goes wrong");
let guess = guess.trim();
- 使用 parse 转换字符串
我们调用
parse方法,将字符串转换为 u32 类型的整数:
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err() => {
println!("Please input valid number");
}
}
let guess: u32 = match guess.trim().parse() {} 这行代码尝试将去除空白符后的字符串解析为
u32类型的整数,因此我们把原来的 guess 变量声明为 u32 整数类型;match:
parse方法转换字符串为整数类型时,可能成功、也可能失败,所以我们使用match表达式允许我们处理每可能的结果 (Ok 和 Err) 并返回。Ok(num) => num: 如果
parse成功,Result 将会是Ok(num), 其中num是解析得到的数值。此处, 我们使用=>将得到的整数num直接返回。Err(_) => {}: 如果
parse失败, Result 会是 Err(), 其中_是一个通配符,表示我们不关心具体的错误信息。并使用continue跳过本次循环,提示用户重新输入。
- 两数比对
我们将使用 std 依赖中的
cmp和Ordering来实现对比功能
cmp(): 方法是泛型, 可以用于比较多种类型的值, 比如整数,字符串,浮点数等。
use std::cmp::Ordering;
fn main() {
let num1 = 42;
let num2 = 30;
match num1.cmp(&num2) {
Ordering::Less => println!("{} 小于 {}", num1, num2),
Ordering::Equal => println!("{} 等于 {}", num1, num2),
Ordering::Greater => println!("{} 大于 {}", num1, num2),
}
}
通过 match() 和 cmp() 方法对比两数, cmp 方法将返回一个 std::cmp::Ordering 的枚举,该枚举类有三个变体: Less, Equal, Greater 我们通过 match() 表达式来根据比较的结果执行不同的操作。
match guess.cmp(&secret_number) {
Ordering::Less => {
println!("too small!");
},
Ordering::Equal => {
println!("Congratulation you're right!");
break;
},
Ordering::Greater => println!("{} 大于 {}", num1, num2),
}