今天我们将学习如何在 Solana 中对 Solidity 的函数可见性和合约继承进行概念化。Solidity 中有四个级别的函数可见性,它们是:
- public - 可从合约内部和外部访问。
- external - 仅可从合约外部访问。
- internal - 可在合约及其继承合约内访问。
- private - 仅可在合约内部访问。
让我们在 Solana 中实现相同的效果吧?
公共函数
从第一天到现在,我们定义的所有函数都是公共函数:
pub fn my_public_function(ctx: Context<Initialize>) -> Result<()> {
// Function logic...
Ok(())
}
在函数声明前添加 pub 关键字可使该函数成为公共函数。
你不能移除带有 #[program] 标签的模块 (mod) 内部函数的 pub 关键字。否则代码将无法编译。
不要太在意 external 和 public 之间的区别
对于 Solana 程序来说,调用自身的公共函数通常不太方便。如果在 Solana 程序中有一个 pub 函数,在实际应用中,你可以将其等同于 Solidity 语境下的 external 函数。
如果你想在同一个 Solana 程序内部调用公共函数,更容易的做法是用一个内部实现函数来封装该公共函数,然后调用这个内部函数。
私有与内部函数
尽管你不能在使用 #[program] 宏的模块内部声明不带 pub 的函数,但你可以在文件内部声明它们。请看以下代码:
use anchor_lang::prelude::*;
declare_id!("F26bvRaY1ut3TD1NhrXMsKHpssxF2PAUQ7SjZtnrLkaM");
#[program]
pub mod func_test {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// -------- calling a "private" function --------
let u = get_a_num();
msg!("{}", u);
Ok(())
}
}
// ------- We declared a non pub function over here -------
fn get_a_num() -> u64 {
2
}
#[derive(Accounts)]
pub struct Initialize {}
这将如预期般运行并记录日志。
如果你只想构建简单的 Solana 程序,这就是你需要了解的有关公共和内部函数的全部内容;但如果你希望更好地组织代码,而不是仅仅在程序外部的文件中声明一堆函数,你可以继续往下看。Rust(以及基于此的 Solana)没有像 Solidity 那样的“类(classes)”,因为 Rust 不是面向对象的。因此,“private”和“internal”的区别在 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<()> {
// Call the internal_function from within its parent module
some_internal_function::internal_function();
Ok(())
}
pub mod some_internal_function {
pub fn internal_function() {
// Internal function logic...
}
}
}
mod do_something {
// Import func_visibility module
use crate::func_visibility;
pub fn some_func_here() {
// Call the internal_function from outside its parent module
func_visibility::some_internal_function::internal_function();
// Do something else...
}
}
#[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<()> {
// Call the private_function from within its parent module
some_function_function::private_function();
Ok(())
}
pub mod some_function_function {
pub(in crate::func_visibility) fn private_function() {
// Private function logic...
}
}
}
#[derive(Accounts)]
pub struct Initialize {}
pub(in crate::func_visibility) 关键字表示 private_function 函数仅在 func_visibility 模块内可见。
我们能够在 initialize 函数中成功调用 private_function,因为 initialize 函数位于 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<()> {
// Call the private_function from within its parent module
some_private_function::private_function();
Ok(())
}
pub mod some_private_function {
pub(in crate::func_visibility) fn private_function() {
// Private function logic...
}
}
}
mod do_something {
// Import func_visibility module
use crate::func_visibility;
pub fn some_func_here() {
// Call the private_function from outside its parent module
func_visibility::some_private_function::private_function()
// Do something...
}
}
#[derive(Accounts)]
pub struct Initialize {}
构建程序。发生了什么?我们得到了一个错误:
❌ error[E0624]: associated function private_function is private
这表明 private_function 不是公开可访问的,且无法从其可见模块外部调用。查看 Rust 文档中的可见性与隐私部分,了解更多关于 pub 可见性关键字的信息。
合约继承
将 Solidity 的合约继承直接转换到 Solana 是不可能的,因为 Rust 没有类。
然而,Rust 中的一种变通方法是创建定义特定功能的独立模块,然后在我们的主程序中使用这些模块,从而实现类似于 Solidity 合约继承的效果。
从另一个文件获取模块
随着程序变大,我们通常不想把所有东西都放在一个文件中。以下是我们如何将逻辑组织到多个文件中的方法。
让我们在 src 文件夹中创建另一个名为 calculate.rs 的文件,并将提供的代码复制到其中。
pub fn add(x: u64, y: u64) -> u64 {
// Return the sum of x and y
x + y
}
这个 add 函数返回 x 和 y 的和。
然后将这些代码复制到 lib.rs 中。
use anchor_lang::prelude::*;
// Import `calculate` module or crate
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<()> {
// Call `add` function in calculate.rs
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<()> {
// Call `add` function in calculate.rs
let result = calculate::add(x, y);
msg!("{} + {} = {}", x, y, result);
Ok(())
}
}
mod calculate {
pub fn add(x: u64, y: u64) -> u64 {
// Return the summation of x and y
x + y
}
}
#[derive(Accounts)]
pub struct Initialize {}
这个程序实现的功能与前一个示例相同,唯一的区别在于 add 函数位于 lib.rs 文件内且在 calculate 模块中。此外,给函数添加 pub 关键字非常关键,因为它使函数变为公开可访问状态。以下代码将无法编译:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod func_visibility {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
// Call the private-like function
let result2 = do_something::some_func_here();
msg!("The result is {}", result2);
Ok(())
}
}
mod do_something {
// private-like function. It exists in the code, but not everyone can call it
fn some_func_here() -> u64 {
// Do something...
return 20;
}
}
#[derive(Accounts)]
pub struct Initialize {}
总结
在 Solidity 中,我们会经常考虑函数可见性,因为它非常关键。以下是在 Rust 中思考它的方式:
- Public / External(公共/外部)函数:这些是可在程序内部和外部访问的函数。在 Solana 中,所有声明的函数默认都是 public(公共的)。
#[program]块中的所有内容都必须声明为pub。 - Internal(内部)函数:这些是可在程序自身及继承该程序的程序中访问的函数。嵌套在 pub mod 块中的函数不包含在构建出的程序中,但它们仍可在父模块内部或外部被访问。
- Private(私有)函数:这些函数不是公开可访问的,也不能从其所在模块外部被调用。在 Rust/Solana 中实现私有可见性,需要在一个特定模块内通过
pub(in crate::<module>)关键字来定义函数,这使得该函数仅在定义它的模块内部可见。
Solidity 通过类来实现合约继承,而 Solana 使用的语言 Rust 没有这个特性。尽管如此,你仍然可以使用 Rust 模块来组织你的代码。
通过 RareSkills 了解更多
本教程是我们免费 Solana 课程的一部分。
最初发布于 2024 年 2 月 17 日