跟我一起从0开始学习Solana合约开发,一起实操,一起做项目。这是一个系列文章,系统地记录了我的学习笔记。
欢迎阅读我的系列文章《Solana笔记》第四讲!经过前面的学习,相信你已经完成了环境配置,并熟悉了Anchor项目。今天,我将带你学习一些关键的 Rust 基础知识,并理解src/lib.rs
主程序。
请你先思考这样一个问题:用anchor init
生成的主程序代码为什么是src/lib.rs
而不是src/main.rs
?
让我们带着这个问题开始今天的内容。
对于刚才的问题,需要了解一些solana运行时的知识,这是一个很深入的话题,不过别担心,我们当前只需要简单了解一下,能够理解你正在开发的程序就行了。更多内容我会在以后专门写文章进行探讨。
main.rs
文件编译后生成的是一个二进制程序(如.exe)。而Solana运行时
只能加载.so文件
并调用其中的函数,不能直接运行独立的二进制程序。因此,我们编写的Solana程序最终都会被编译为.so文件,之后再部署到链上存储在对应的账户(Account)中,每个Solana程序对应唯一的.so文件。当用户提交交易调用某个程序时,Solana节点会从账户中加载对应的.so文件到内存,执行其中的逻辑。
为了生成.so文件
,需要用到rust的库项目(默认入口文件为lib.rs),而不是rust的二进制程序。另外,还需要在Cargo.toml
中配置crate-type
。
打开programs/guide_1/Cargo.toml
,可以看到如下配置:
[lib]
crate-type = ["cdylib", "lib"]
name = "guide_1"
在这段配置中,name
指定了库的名称,在生成的.so文件中会使用这个名称;crate-type
指定了库项目的两种输出类型:
1. cdylib: 生成符合 C 动态库标准的共享对象文件(.so),适合在 Solana 上运行。
2. lib: 生成 Rust 专用的静态库文件(.rlib),通常用于测试或其他 Rust 项目的依赖。
通过这种配置,Rust编译器会为库项目生成两种类型的输出文件,我列个表格对比一下:
cdylib | lib | |
---|---|---|
输出文件 | .so文件 | 静态库文件(.rlib) |
谁使用 | Solana 或其他非Rust平台加载运行 | Rust自己使用 |
用途 | 部署至Solana网络 | 本地开发和测试时作为依赖 |
特点 | 通用的,可以被外部程序(如 Solana)加载 | Rust专用 |
如果只配置 "cdylib",不配置 "lib" 可以吗?
仍然可以生成 .so 文件供 Solana 使用
。但本地开发和测试可能会更麻烦,因为无法直接引用库中的函数,需要通过模拟或额外的设置来测试。所以,同时设置 cdylib 和 lib 是一种既方便开发又支持部署的常见做法。
Rust 的宏是一个强大而复杂的工具,为了简化学习、少走弯路,并专注于 Solana 程序开发,暂时不需要掌握 Rust 所有的宏知识。可以先简单理解为:
宏就像是代码模板,使用宏可以自动生成重复的代码,帮助简化开发工作。
以下是一些在 Solana 开发中经常用到的宏。掌握这些,就足以阅读 Solana 源码和开发 Solana 程序了。
1. declare_id!
declare_id!
宏用于声明程序的 Program ID。
2. #[program]
#[program]
宏是 Anchor 框架提供的,用于声明一个模块为 Solana 程序。可以理解为程序的入口。一个程序里只能有一个 #[program] 宏。
3. msg!
在 Solana 链上的执行环境中,由于无法直接使用 Rust 的 println!
宏,Anchor 框架提供了 msg!
宏,这是专为 Solana 链上环境设计的日志工具,可用于输出日志信息以便调试。举个例子:
let user = "xxxxxxx";
let amount = 42;
// 使用 msg! 输出日志
msg!("User: {}, transferred amount: {}", user, amount);
这段代码展示了如何使用 msg! 宏记录用户和转账金额等信息,以便在链上运行时查看日志。
4. #[derive(Accounts)]
#[derive(Accounts)]
宏用于处理账户验证,可以帮助你减少繁琐的账户验证代码。
先简单知道这个宏的作用即可,因为这个宏涉及到 Solana 账户模型,后面单独写一篇文章再详细讲解。
这里只讲一个 vec! 宏。vec! 是 Rust 标准库提供的一个宏,用于快速创建动态数组(即 Vec<T>
类型)。Vec 是一种可增长的数组类型,你可以使用 push 方法向数组中添加元素。
你可以将 vec! 类比为 Solidity 中的动态数组,二者都允许你向数组中添加元素。然而,vec! 与 Solidity 的动态数组在运行环境、存储位置和操作成本上有显著不同。以后我会专门写文章对比这两者。
1. vec! 无默认值的用法
// 创建一个空的 Vec,并通过 push 添加元素
let mut data1 = vec![]; // 创建一个空的 Vec
data1.push(42); // 向 Vec 中添加一个元素 42
data1.push(7); // 向 Vec 中添加另一个元素 7
// 使用 msg! 打印 Vec
msg!("{:?}", data1); // 输出: [42, 7]
这里使用了 mut
关键字。在 Rust 中,默认情况下,变量是不可变的。也就是说,如果没有 mut
修饰符,变量就不能执行如 push
这样的操作。
2. vec! 有默认值的用法
// 创建一个长度为 1024 的字节数组,初始化每个元素为 0
let data2: Vec<u8> = vec![0; 1024];
// 使用 msg! 打印 Vec
msg!("{:?}", data2); // 输出: [0, 0, 0, ..., 0] (1024个0)
相关解释:
vec![0; 1024]
: 通过 vec![value; size]
可以创建一个带有默认值的 Vec,其中 value 是每个元素的默认值,size 是数组的长度。
{}
:用于打印简单类型(如整数、浮点数等)。
{:?}
:用于打印更复杂的类型(如数组、结构体等)。在这里,{:?} 格式化符号允许我们查看整个数组的内容。
关于 vec!
,随着学习的深入,我会在后续补充更多具体的应用场景和实际开发中的使用示例,帮助加深对其的理解。今天先了解这些基础内容即可。
这里和以太坊中 solidity 语言的数据类型对比着学习。
Pubkey
可以类比为 solidity
中的 address
。用于标识账户、程序或智能合约的地址。
let pubkey = Pubkey::new_from_array([0; 32]);
u64
可以类比为 solidity
中的 uint256
。Rust 中并没有与 uint256 完全相同的类型,但 u64 能满足大部分需要处理整数的场景。Solana 程序通常使用 u64 来处理数字较大的需求。
let balance: u64 = 1000;
bool
和 solidity
中的 bool
一样。
let is_active: bool = true;
Vec<u64>
可以类比为 solidity
中的 uint256[]
。上面讲过了,不再赘述。
HashMap<u64, u64>
和 BTreeMap<u64, u64>
可以类比为 solidity
中的 mapping(uint256 => uint256)
。是一种键值对数据结构,类似于哈希表。
HashMap 适用于无序的键值对存储,而 BTreeMap 用于需要按顺序存储键值对的场景。
use std::collections::HashMap;
use std::collections::BTreeMap;
let mut hmap: HashMap<u64, u64> = HashMap::new();
hmap.insert(1, 1001);
let mut bmap: BTreeMap<u64, u64> = BTreeMap::new();
bmap.insert(2, 1002);
学习编程,实践才是关键!下面我们将以上讲过的所有代码在本地运行测试,看看效果。
准备工作
打开两个终端:
solana-test-validator
启动本地节点。 solana logs
查看程序日志。 src/lib.rs
文件的完整代码如下:
use anchor_lang::prelude::*;
use std::collections::HashMap;
use std::collections::BTreeMap;
declare_id!("7pR9Lstthnphgx2bLFo6WW4gbRJmGiJDujL4wxYRFRWg");
#[program]
pub mod guide_1 {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Greetings from: {:?}", ctx.program_id);
// 简单类型
let pubkey = Pubkey::new_from_array([0; 32]);
let balance: u64 = 1000;
let is_active: bool = true;
msg!("pubkey: {:?}, balance: {}, is_active: {}", pubkey, balance, is_active);
// 映射类型
let mut hmap: HashMap<u64, u64> = HashMap::new();
hmap.insert(1, 1001);
let mut bmap: BTreeMap<u64, u64> = BTreeMap::new();
bmap.insert(2, 1002);
msg!("HashMap: {:?}, BTreeMap: {:?}", hmap, bmap);
// 数组类型(无默认值)
let mut data1 = vec![];
data1.push(42);
data1.push(7);
msg!("data1: {:?}", data1);
// 数组类型(有默认值)
let data2: Vec<u8> = vec![0; 1024];
msg!("data2: {:?}", data2);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
现在,执行测试命令:
anchor test --skip-local-validator
报错了吗?我估计大部分人会报错,下一篇我来讲解常见的报错问题,带你彻底理解并解决初学者很容易就遇到的各种报错。如果你想提前看到我的更新,可以关注我的公众号:认知那些事
。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!