本教程概述了 Solidity 中最常用的语法,并演示了在 Rust 中的等效实现。
如果你想高层次地了解 Rust vs Solidity 的差异,请参阅链接中的教程。本教程假设你已经了解 Solidity,如果你对 Solidity 不熟悉,请查看我们免费的 Solidity 教程。
创建一个名为 tryrust 的新 Solana Anchor 项目并设置环境。
条件语句
可以说在 Solidity 中,开发者可以通过 2 种方式根据特定条件控制执行流程:
- If-Else 语句
- 三元运算符
现在让我们看看上述语句在 Solidity 中的样子,以及它们在 Solana 中的转换形式。
If-Else 语句
在 Solidity 中:
function ageChecker(uint256 age) public pure returns (string memory) {
if (age >= 18) {
return "You are 18 years old or above";
} else {
return "You are below 18 years old";
}
}
在 Solana 中,在 lib.rs 中添加一个名为 age_checker 的新函数:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
if age >= 18 {
msg!("You are 18 years old or above");
} else {
msg!("You are below 18 years old");
}
Ok(())
}
请注意,条件 age >= 18 没有括号——在 if 语句中括号是可选的。
为了进行测试,在 ./tests/tryrust.ts 中添加另一个 it 块:
it("Age checker", async () => {
// Add your test here.
const tx = await program.methods.ageChecker(new anchor.BN(35)).rpc();
console.log("Your transaction signature", tx);
});
运行测试后我们应该会得到以下日志:
Transaction executed in slot 77791:
Signature: 2Av18ej2YjkRhzybbccPpwEtkw73VcBpDPZgC9iKrmf6mvwbqjA517garhrntWxKAM1ULL2eAv5vDWJ3SjnFZq6j
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: AgeChecker
Program log: You are 18 years old or above
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 {"You are 18 years old or above"} else { "You are below 18 years old" };
msg!("{:?}", result);
Ok(())
}
请注意,在 Rust 的三元运算符示例中,if/else 块以分号结尾,因为这是在给变量赋值。
还要注意,内部值末尾没有分号,因为它是作为返回值返回给变量的,类似于你不会在 Ok(()) 后面加分号,因为它是一个表达式而不是一个语句。
如果 age 是偶数,程序记录 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 => {
// Code block executed if age equals 1
msg!("The age is 1");
},
2 | 3 => {
// Code block executed if age equals 2 or 3
msg!("The age is either 2 or 3");
},
4..=6 => {
// Code block executed if age is in the
// range 4 to 6 (inclusive)
msg!("The age is between 4 and 6");
},
_ => {
// Code block executed for any other age
msg!("The age is something else");
}
}
Ok(())
}
For 循环
正如我们所知,for 循环允许遍历范围、集合和其他可迭代对象,在 Solidity 中是这样写的:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i++) {
// do something...
}
}
这是在 Solana (Rust) 中的等效代码:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in 0..10 {
// do something...
}
Ok(())
}
是的,就这么简单,但是我们如何以自定义步长遍历一个范围呢?以下是 Solidity 中的预期行为:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i+=2) {
// do something...
// Increment i by 2
}
}
以下是在 Solana 中使用 step_by 的等效代码:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in (0..10).step_by(2) {
// do something...
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 仅内置支持固定长度数组。如果你想要一个动态长度的列表,请使用向量(vector)。
现在,让我们看一些演示如何声明和初始化固定数组与动态数组的示例。
固定数组
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Declare an array of u32 with a fixed size of 5
let my_array: [u32; 5] = [10, 20, 30, 40, 50];
// Accessing elements of the array
let first_element = my_array[0];
let third_element = my_array[2];
// Declare a mutable array of u32 with a fixed size of 3
let mut mutable_array: [u32; 3] = [100, 200, 300];
// Change the second element from 200 to 250
mutable_array[1] = 250;
// Rest of your program's logic
Ok(())
}
动态数组
在 Solana 中模拟动态数组的一种方法是使用 Rust 标准库中的 Vec(向量)。以下是一个示例:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Declare a dynamic array-like structure using Vec
let mut dynamic_array: Vec<u32> = Vec::new();
// Add elements to the dynamic array
dynamic_array.push(10);
dynamic_array.push(20);
dynamic_array.push(30);
// Accessing elements of the dynamic array
let first_element = dynamic_array[0];
let third_element = dynamic_array[2];
// Rest of your program's logic
msg!("Third element = {}", third_element);
Ok(())
}
dynamic_array 变量必须声明为可变 (mut) 才能允许发生改变(如 push、pop、在某个索引处覆盖等)。
运行测试后,程序应该记录如下信息:
Transaction executed in slot 195373:
Signature: 4113irrcBsFbNaiZia5c84yfJpS4Hn4H1QawfUSHYoPuuQPj22JnVFtDMHmZDFkQ3vK15SrDUSTakh5fT4N8UVRf
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: Third element = 30
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 1010 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
映射
与 Solidity 不同,Solana 缺乏内置的映射数据结构。不过,我们可以利用 Rust 标准库中的 HashMap 类型在 Solana 中复制键值映射的功能。与 EVM 链不同的是,我们这里演示的 map 是在内存中,而不是在存储中。EVM 链没有内存中的哈希映射。我们稍后将演示 Solana 在存储中的映射。
让我们看看如何在 Solana 中使用 HashMap 创建映射。将提供的代码片段复制并粘贴到 lib.rs 文件中,并记得将程序 ID 替换为你自己的:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod tryrust {
use super::*;
// Import HashMap library
use std::collections::HashMap;
pub fn initialize(ctx: Context<Initialize>, key: String, value: String) -> Result<()> {
// Initialize the mapping
let mut my_map = HashMap::new();
// Add a key-value pair to the mapping
my_map.insert(key.to_string(), value.to_string());
// Log the value corresponding to a key from the mapping
msg!("My name is {}", my_map[&key]);
Ok(())
}
}
my_map 变量也被设为可变的,以便我们可以对其进行编辑(即添加/删除键值对)。还注意到我们是如何导入 HashMap 库的吗?
由于 initialize 函数接收两个参数,因此测试代码也需要更新:
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize("name", "Bob").rpc();
console.log("Your transaction signature", tx);
});
当我们运行测试时,会看到以下日志:
Transaction executed in slot 216142:
Signature: 5m4Cx26jaYT3c6YeJbLMDHppvki4Kmu3zTDMgk8Tao9v8b9sH7WgejETzymnHuUfr4hY25opptqniBuwDpncbnB9
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: My name is Bob
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2634 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
结构体
在 Solidity 和 Solana 中,结构体被用来定义可以包含多个字段的自定义数据结构。让我们看看 Solidity 和 Solana 中的结构体示例。
在 Solidity 中:
contract SolidityStructs {
// Defining a struct in Solidity
struct Person {
string my_name;
uint256 my_age;
}
// Creating an instance of the struct
Person person1;
function initPerson1(string memory name, uint256 age) public {
// Accessing and modifying struct fields
person1.my_name = name;
person1.my_age = age;
}
}
在 Solana 中一一对应的代码:
pub fn initialize(_ctx: Context<Initialize>, name: String, age: u64) -> Result<()> {
// Defining a struct in Solana
struct Person {
my_name: String,
my_age: u64,
}
// Creating an instance of the struct
let mut person1: Person = Person {
my_name: name,
my_age: age,
};
msg!("{} is {} years old", person1.my_name, person1.my_age);
// Accessing and modifying struct fields
person1.my_name = "Bob".to_string();
person1.my_age = 18;
msg!("{} is {} years old", 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 is 20 years old
Program log: Bob is 18 years old
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2601 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
在提供的代码片段中,Solidity 实现将结构体实例存储在 storage 中,而在 Solana 实现中,所有操作都发生在 initialize 函数内,没有将任何内容存储在链上。关于存储的内容将在后续教程中讨论。
Rust 中的常量
在 Rust 中声明常量变量非常简单。只需使用 const 关键字代替 let 关键字即可。这些常量可以在 #[program] 块外部声明。
use anchor_lang::prelude::*;
declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");
// *** CONSTANT DECLARED HERE ***
const MEANING_OF_LIFE_AND_EXISTENCE: u64 = 42;
#[program]
pub mod tryrust {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!(&format!("Answer to the ultimate question: {}", MEANING_OF_LIFE_AND_EXISTENCE)); // new line here
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
usize 类型与类型转换
在绝大多数情况下,我们可以假定 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(); // this has type usize
let another_var: u64 = 5; // this has type u64
let len_plus_another_var = len as u64 + another_var;
msg!("The result is {}", len_plus_another_var);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
Try Catch
Rust 没有 try catch。对于失败情况,通常预期返回错误(就像我们在关于 Solana 回滚和错误的教程中所做的那样),或者对于不可恢复的错误引发 panic。
练习:编写一个 Solana / Rust 程序,接收一个 u64 的向量,循环遍历它,并将所有偶数推入另一个向量中,然后打印这个新向量。
在 RareSkills 了解更多
本教程是我们免费 Solana 课程 的一部分。
原文发布于 2024 年 2 月 13 日