Skip to content

Latest commit

 

History

History
414 lines (290 loc) · 12.8 KB

chapter_6.md

File metadata and controls

414 lines (290 loc) · 12.8 KB

面向 Solidity 开发人员的基本 Rust 知识

更新日期:Feb 29

Rust 基本语法

本教程介绍了 Solidity 中最常用的语法,并演示了 Rust 中的类似语法。

如果你想要了解 Rust vs Solidity 的区别,请参考链接的教程。本教程假定你已经了解 Solidity,如果你对 Solidity 不熟悉,请参阅我们提供的免费 Solidity 教程

创建一个新的 Solana Anchor 项目,名为 tryrust ,并设置环境。

条件语句

在 Solidity 中有两种开发人员可以根据特定条件控制执行流程的方式:

  • 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);
});

运行测试后,我们应该看到以下日志:

rust 测试控制台

三元运算符

在 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:

rust 测试控制台布尔值

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(())
}

运行测试后,我们应该看到以下日志:

rust for 循环

数组和 Vector

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、在索引处覆盖等)。

运行测试后,程序应该记录如下:

rust 向量

映射

与 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::*;
		// 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);
});

运行测试时,我们看到以下日志:

rust 哈希映射

结构体

在 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 函数并运行测试,你应该得到以下日志:

rust 结构体

在提供的代码片段中,Solidity 将结构体的实例存储在存储中,而 Solana 实现中,一切都发生在 initialize 函数中,没有任何东西存储在链上。存储将在以后的教程中讨论。

Rust 中的常量

在 Rust 中声明常量变量很简单。不使用 let 关键字,而是使用 const 关键字。这些可以在 #[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。

练习: 编写一个接受 u64 向量、循环遍历它并将所有偶数写入到另一个向量,然后打印新向量的 Solana / Rust 程序。

通过 RareSkills 了解更多

本教程是我们免费的 Solana 课程 的一部分。