本教程深入探讨了Solidity和Rust在控制流、数组、映射、结构体和常量等方面的语法对比,旨在帮助掌握Solidity的开发者快速上手Rust编程。
本教程介绍了在 Solidity 中最常用的语法,并展示了其在 Rust 中的等价实现。
如果你想了解 Rust 与 Solidity 之间的高层次差异,请查看链接的教程。本教程假设你已经了解 Solidity,假如你对 Solidity 不熟悉,请查看我们免费的 Solidity 教程。
创建一个名为 tryrust
的新 Solana Anchor 项目并设置环境。
我们可以说,开发人员在 Solidity 中可以根据特定条件控制执行流程的方式有 2 种:
现在让我们看看上述内容在 Solidity 中的实现,以及它们在 Solana 中的翻译。
在 Solidity 中:
function ageChecker(uint256 age) public pure returns (string memory) {
if (age >= 18) {
return "你已满 18 岁或以上";
} else {
return "你未满 18 岁";
}
}
在 Solana 中,在 lib.rs 中添加一个名为 age_checker
的新函数:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
if age >= 18 {
msg!("你已满 18 岁或以上");
} else {
msg!("你未满 18 岁");
}
Ok(())
}
请注意,条件 age >= 18
没有括号——这在 if 语句中是可选的。
要测试,请在 ./tests/tryrust.ts
中添加另一个 it
块:
it("年龄检查", async () => {
// 在这里添加你的测试。
const tx = await program.methods.ageChecker(new anchor.BN(35)).rpc();
console.log("你的交易签名", tx);
});
运行测试后,我们应该得到以下日志:
Transaction executed in slot 77791:
Signature: 2Av18ej2YjkRhzybbccPpwEtkw73VcBpDPZgC9iKrmf6mvwbqjA517garhrntWxKAM1ULL2eAv5vDWJ3SjnFZq6j
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: AgeChecker
Program log: 你已满 18 岁或以上
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 440 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
在 Solidity 中将 if-else 语句分配给变量:
function ageChecker(uint256 age) public pure returns (bool a) {
a = age % 2 == 0 ? true : false;
}
在 Solana 中,我们基本上只是将一个 if-else 语句分配给一个变量。下面的 Solana 程序与上述相同:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
let result = if age >= 18 {"你已满 18 岁或以上"} else { "你未满 18 岁" };
msg!("{:?}", result);
Ok(())
}
请注意,在 Rust 的三元运算符示例中,if/else 块以分号结尾,因为这是分配给一个变量的。
还要注意,内部值没有以分号结尾,因为它作为返回值返回给变量,就像在表达式而不是语句的情况下不在 Ok(())
之后加分号一样。
如果年龄是偶数,程序将输出 true,否则为 false:
Transaction executed in slot 102358:
Signature: 2zohZKhY56rLb7myFs8kabdwULJALENyvyFS5LC6yLM264BnkwsThMnotHNAssJbQEzQpmK4yd3ozs3zhG3GH1Gx
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: AgeChecker
Program log: true
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 792 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Rust 还有一个更强大的控制流结构叫做 match。让我们看看下面的使用 match 的示例:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
match age {
1 => {
// 如果年龄等于 1 则执行的代码块
msg!("年龄是 1");
},
2 | 3 => {
// 如果年龄等于 2 或 3 则执行的代码块
msg!("年龄是 2 或 3");
},
4..=6 => {
// 如果年龄在 4 到 6(包括)之间则执行的代码块
msg!("年龄在 4 到 6 之间");
},
_ => {
// 任何其他年龄的代码块
msg!("年龄是其他值");
}
}
Ok(())
}
我们知道,for 循环允许遍历范围、集合和其他可迭代对象,在 Solidity 中是这样写的:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i++) {
// 做一些事情...
}
}
这在 Solana(Rust)中的等价实现为:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in 0..10 {
// 做一些事情...
}
Ok(())
}
是的,简单如斯,但我们如何使用自定义步长遍历范围呢?以下是在 Solidity 中的预期行为:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i+=2) {
// 做一些事情...
// 将 i 增加 2
}
}
在 Solana 中使用 step_by
的等价实现是:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in (0..10).step_by(2) {
// 做一些事情...
msg!("{}", i);
}
Ok(())
}
运行测试后,我们应该获得以下日志:
Transaction executed in slot 126442:
Signature: 3BSPA11TZVSbF8krjMnge1fgwNsL9odknD2twAsDeYEF39AzaJy1c5TmFCt6LEzLtvWnjzx7VyFKJ4VT1KQBpiwm
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: 0
Program log: 2
Program log: 4
Program log: 6
Program log: 8
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2830 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Rust 和 Solidity 在数组支持上有所不同。虽然 Solidity 原生支持固定和动态数组,但 Rust 仅内置支持固定数组。如果你想要动态长度的列表,请使用向量。
现在,让我们看一些示例,演示如何声明和初始化固定和动态数组。
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// 声明一个固定大小为 5 的 u32 数组
let my_array: [u32; 5] = [10, 20, 30, 40, 50];
// 访问数组元素
let first_element = my_array[0];
let third_element = my_array[2];
// 声明一个固定大小为 3 的可变 u32 数组
let mut mutable_array: [u32; 3] = [100, 200, 300];
// 将第二个元素从 200 修改为 250
mutable_array[1] = 250;
// 你程序的其余逻辑
Ok(())
}
在 Solana 中模拟动态数组的方法涉及使用来自 Rust 标准库的 Vec
(向量)。以下是一个示例:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// 使用 Vec 声明一个类似动态数组的结构
let mut dynamic_array: Vec<u32> = Vec::new();
// 向动态数组添加元素
dynamic_array.push(10);
dynamic_array.push(20);
dynamic_array.push(30);
// 访问动态数组的元素
let first_element = dynamic_array[0];
let third_element = dynamic_array[2];
// 你程序的其余逻辑
msg!("第三个元素 = {}", third_element);
Ok(())
}
dynamic_array
变量必须声明为可变(mut
),以允许进行变更(推送、弹出、覆盖索引等)。
程序在运行测试后应该记录如下内容:
Transaction executed in slot 195373:
Signature: 4113irrcBsFbNaiZia5c84yfJpS4Hn4H1QawfUSHYoPuuQPj22JnVFtDMHmZDFkQ3vK15SrDUSTakh5fT4N8UVRf
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: 第三个元素 = 30
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 1010 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
与 Solidity 不同,Solana 缺少内置的映射数据结构。然而,我们可以通过利用 Rust 标准库中的 HashMap 类型在 Solana 中复制键值映射的功能。与 EVM 链不同的是,我们在这里展示的映射是在内存中的,而不是存储中的。EVM 链没有内存哈希映射。我们将在稍后的教程中展示 Solana 中的存储映射。
让我们看看如何使用 HashMap
在 Solana 中创建一个映射。将提供的代码片段复制并粘贴到 lib.rs 文件中,并记得将程序 ID 替换为你自己的:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod tryrust {
use super::*;
// 导入 HashMap 库
use std::collections::HashMap;
pub fn initialize(ctx: Context<Initialize>, key: String, value: String) -> Result<()> {
// 初始化映射
let mut my_map = HashMap::new();
// 向映射中添加键值对
my_map.insert(key.to_string(), value.to_string());
// 记录映射中与键对应的值
msg!("我的名字是 {}", my_map[&key]);
Ok(())
}
}
my_map
变量也被声明为可变,以便进行编辑(即,添加/删除键→值对)。还注意我们是如何导入 HashMap
库的?
由于 initialize
函数接收两个参数,因此测试也需要进行更新:
it("已初始化!", async () => {
// 在这里添加你的测试。
const tx = await program.methods.initialize("name", "Bob").rpc();
console.log("你的交易签名", tx);
});
当我们运行测试时,会看到以下日志:
Transaction executed in slot 216142:
Signature: 5m4Cx26jaYT3c6YeJbLMDHppvki4Kmu3zTDMgk8Tao9v8b9sH7WgejETzymnHuUfr4hY25opptqniBuwDpncbnB9
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: 我叫 Bob
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2634 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
在 Solidity 和 Solana 中,结构体用于定义可以包含多个字段的自定义数据结构。让我们看看在 Solidity 和 Solana 中的结构体示例。
在 Solidity 中:
contract SolidityStructs {
// 在 Solidity 中定义结构体
struct Person {
string my_name;
uint256 my_age;
}
// 创建结构体的实例
Person person1;
function initPerson1(string memory name, uint256 age) public {
// 访问和修改结构体字段
person1.my_name = name;
person1.my_age = age;
}
}
在 Solana 中与之对应的实现:
pub fn initialize(_ctx: Context<Initialize>, name: String, age: u64) -> Result<()> {
// 在 Solana 中定义结构体
struct Person {
my_name: String,
my_age: u64,
}
// 创建结构体的实例
let mut person1: Person = Person {
my_name: name,
my_age: age,
};
msg!("{} 的年龄为 {} 岁", person1.my_name, person1.my_age);
// 访问和修改结构体字段
person1.my_name = "Bob".to_string();
person1.my_age = 18;
msg!("{} 的年龄为 {} 岁", person1.my_name, person1.my_age);
Ok(())
}
练习 : 更新测试文件以传递两个参数 Alice 和 20 给 initialize 函数并运行测试,你应该得到以下日志:
Transaction executed in slot 324406:
Signature: 2XBQKJLpkJbVuuonqzirN9CK5dNKnuu5NqNCGTGgQovWBfrdjRcVeckDmqtzyEPe4PP8xSN8vf2STNxWygE4BPZN
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: Alice 的年龄为 20 岁
Program log: Bob 的年龄为 18 岁
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2601 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
在提供的代码片段中,Solidity 实现将结构体的实例存储在存储中,而在 Solana 实现中,所有操作都发生在初始化函数中,且没有在链上存储。存储将在以后的教程中讨论。
在 Rust 中声明常量变量非常简单。使用 const 关键字而不是 let 关键字。可以在 #[program]
块外声明这些常量。
use anchor_lang::prelude::*;
declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");
// *** 在这里声明常量 ***
const MEANING_OF_LIFE_AND_EXISTENCE: u64 = 42;
#[program]
pub mod tryrust {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!(&format!("终极问题的答案: {}", MEANING_OF_LIFE_AND_EXISTENCE)); // 这里的新行
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
大多数时候我们可以假定在 Solana 中的无符号整数类型为 u64
,但在测量列表的长度时,有一个例外:它将是 usize 类型。你需要将变量转换,如以下 Rust 代码所示:
use anchor_lang::prelude::*;
declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");
#[program]
pub mod usize_example {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let mut dynamic_array: Vec<u32> = Vec::from([1,2,3,4,5,6]);
let len = dynamic_array.len(); // 这个类型为 usize
let another_var: u64 = 5; // 这个类型为 u64
let len_plus_another_var = len as u64 + another_var;
msg!("结果是 {}", len_plus_another_var);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
Rust 没有 try catch。故障预期会返回错误(就像我们在 Solana 回滚和错误的教程中所做的)或对于不可恢复性错误引发恐慌。
练习 : 编写一个 Solana / Rust 程序,接受一个 u64 向量,遍历它,将所有偶数推送到另一个向量中,然后打印新的向量。
本教程是 Solana 课程 的一部分。
最初发布于 2024 年 2 月 13 日
- 原文链接: rareskills.io/post/rust-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!