Solana基础 - Rust 和 Solana 中的可见性与“继承”
本文详细讲解了如何将Solidity中的函数可见性和合约继承概念化到Solana中,并提供了Rust语言中实现这些概念的代码示例。
今天我们将学习如何在 Solana 中构思 Solidity 的函数可见性和合约继承。Solidity 中有四种函数可见性级别,它们是:
- public – 从合约内部和外部均可访问。
- external – 从合约外部仅可访问。
- internal – 从合约内部和继承合约中可访问。
- private – 仅在合约内部可访问。
让我们在 Solana 中实现相同的功能,好吗?
公共函数
自第一天起到现在我们定义的所有函数都是公共函数:
pub fn my_public_function(ctx: Context<Initialize>) -> Result<()> {
// 函数逻辑...
Ok(())
}在函数声明前添加 pub 关键字,从而使函数变为公共。
你不能移除标记为 #[program] 的模块 (mod) 内部的函数的 pub 关键字。这将导致无法编译。
不用太担心 external 和 public 之间的区别
在 Solana 程序中,调用自己公共函数通常是不方便的。如果 Solana 程序中存在 pub 函数,从实际角度来看,你可以将其视作为在 Solidity 中的外部函数。
如果你想在同一个 Solana 程序中调用公共函数,包裹这个公共函数并调用该内部实现函数要更容易。
私有和内部函数
虽然你不能在使用 #[program] 宏的模块中声明没有 pub 的函数,但你可以在文件内部声明函数。考虑以下代码:
use anchor_lang::prelude::*;
declare_id!("F26bvRaY1ut3TD1NhrXMsKHpssxF2PAUQ7SjZtnrLkaM");
#[program]
pub mod func_test {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// -------- 调用一个“私有”函数 --------
let u = get_a_num();
msg!("{}", u);
Ok(())
}
}
// ------- 我们在此声明了一个非 pub 函数 -------
fn get_a_num() -> u64 {
2
}
#[derive(Accounts)]
pub struct Initialize {}这将按预期运行并记录。
如果你想构建简单的 Solana 程序,这些是你真正需要了解的有关公共和内部函数的所有内容。但如果你希望比仅仅在文件中声明一堆函数更好地组织代码,你可以继续深入。Rust,因此 Solana,没有像 Solidity 那样的“类”,因为 Rust 不是面向对象的。因此,“私有”和“内部”之间的区别在 Rust 中没有直接类比。
Rust 使用模块来组织代码。模块内外函数的可见性在Rust 文档的可见性和隐私部分中有很好的讨论,但我们将添加自己针对 Solana 的见解。
内部函数
通过在程序模块内定义函数并确保其在其自己的模块及导入或使用它的其他模块内可访问来实现。这是怎么做的:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
// 从其父模块内部调用 internal_function
some_internal_function::internal_function();
Ok(())
}
pub mod some_internal_function {
pub fn internal_function() {
// 内部函数逻辑...
}
}
}
mod do_something {
// 导入 func_visibility 模块
use crate::func_visibility;
pub fn some_func_here() {
// 从外部其父模块调用 internal_function
func_visibility::some_internal_function::internal_function();
// 做其他事情...
}
}
#[derive(Accounts)]
pub struct Initialize {}构建程序后,如果导航到 ./target/idl/func_visibility.json 文件,你将看到定义在 some_internal_function 模块中的函数没有被包含在构建程序中。这表明 some_internal_function 函数是内部的,仅能在程序内部及任何导入或使用它的程序中访问。
从上面的例子中,我们能够从其“父”模块(func_visibility)内部访问 internal_function 函数,也能从 func_visibility 模块外的单独模块中访问(do_something)。
私有函数
在特定模块内定义函数并确保它们不会暴露在该作用域之外就是实现私有可见性的一种方式:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
// 从其父模块内部调用 private_function
some_function_function::private_function();
Ok(())
}
pub mod some_function_function {
pub(in crate::func_visibility) fn private_function() {
// 私有函数逻辑...
}
}
}
#[derive(Accounts)]
pub struct Initialize {}pub(in crate::func_visibility) 关键字表明 private_function 函数仅在 func_visibility 模块中可见。
我们能够成功在初始化函数中调用 private_function,因为初始化函数处于 func_visibility 模块内。让我们尝试从模块外部调用 private_function:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
// 从其父模块内部调用 private_function
some_private_function::private_function();
Ok(())
}
pub mod some_private_function {
pub(in crate::func_visibility) fn private_function() {
// 私有函数逻辑...
}
}
}
mod do_something {
// 导入 func_visibility 模块
use crate::func_visibility;
pub fn some_func_here() {
// 从外部其父模块调用 private_function
func_visibility::some_private_function::private_function()
// 做一些事情...
}
}
#[derive(Accounts)]
pub struct Initialize {}构建程序。发生了什么?我们得到了一个错误:
❌ error[E0624]: associated functionprivate_function is private
这表明 private_function 不是公开可访问的,无法从其可见的模块外部调用。查看Rust 文档中的可见性和隐私以了解更多关于 pub 可见性关键字的信息。
合约继承
将 Solidity 合约继承直接翻译为 Solana 是不可能的,因为 Rust 没有类。
然而,Rust 中的解决方法涉及创建定义特定功能的单独模块,然后在我们的主程序中使用这些模块,从而实现类似于 Solidity 的合约继承的效果。
从另一个文件获取模块
随着程序变大,我们通常不希望将所有内容放入一个文件中。以下是如何将逻辑组织到多个文件中的示例。
在 src 文件夹下创建一个叫 calculate.rs 的文件,并将提供的代码复制到其中。
pub fn add(x: u64, y: u64) -> u64 {
// 返回 x 和 y 的和
x + y
}这个 add 函数返回 x 和 y 的和。
以及将其放入 lib.rs 中。
use anchor_lang::prelude::*;
// 导入 `calculate` 模块或库
pub mod calculate;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn add_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) -> Result<()> {
// 调用 calculate.rs 中的 `add` 函数
let result = calculate::add(x, y);
msg!("{} + {} = {}", x, y, result);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}在上面的程序中,我们导入了之前创建的 calculate 模块,并声明了一个名为 add_two_numbers 的函数,该函数添加两个数字并记录结果。add_two_numbers 函数调用 calculate 模块中的 add 函数,将 x 和 y 作为参数传递,然后将返回值存储在 result 变量中。msg! 宏记录了两个相加的数字和结果。
模块不必是单独的文件
以下示例在 lib.rs 中声明一个模块,而不是在 calculate.rs 中。
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn add_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) -> Result<()> {
// 调用 calculate.rs 中的 `add` 函数
let result = calculate::add(x, y);
msg!("{} + {} = {}", x, y, result);
Ok(())
}
}
mod calculate {
pub fn add(x: u64, y: u64) -> u64 {
// 返回 x 和 y 的总和
x + y
}
}
#[derive(Accounts)]
pub struct Initialize {}这个程序与前一个示例的功能相同,唯一的区别是 add 函数出现在 lib.rs 文件和计算模块内。此外,向函数添加 pub 关键字非常重要,因为这使函数对外公开可访问。以下代码将无法编译:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
// 调用私有类函数
let result2 = do_something::some_func_here();
msg!("The result is {}", result2);
Ok(())
}
}
mod do_something {
// 私有类函数。它存在于代码中,但并不是每个人都可以调用它
fn some_func_here() -> u64 {
// 做一些事情...
return 20;
}
}
#[derive(Accounts)]
pub struct Initialize {}总结
在 Solidity 中,我们非常重视函数可见性,因为它非常关键。以下是我们在 Rust 中的思考方式:
- 公共/外部函数:这些是可以在程序内部和外部访问的函数。在 Solana 中,所有声明的函数默认都是公共的。
#[program]块中的所有内容必须声明为 pub。 - 内部函数:这些是可在程序内部及继承该程序的程序中访问的函数。在嵌套的 pub mod 块内的函数不会包含在构建程序中,但仍然可以在父模块内或外访问。
- 私有函数:这些是不能公开访问的函数,无法从其所在模块外部调用。在 Rust/Solana 中实现私有可见性的方法是定义一个位于特定模块内的函数,使用
pub(in crate::<module>)关键字,使得函数仅在定义它的模块内可见。
Solidity 通过类实现合约继承,而 Rust,Solana 使用的语言,并没有这个特性。尽管如此,你仍然可以使用 Rust 模块来组织代码。