Today we will be learning how Solidity’s function visibility and contract inheritance can be conceptualized in Solana. There are four levels of function visibility in Solidity, they are:
public – accessible from within the contract and externally.
external – accessible from outside the contract only.
internal – accessible within the contract and inheriting contracts.
private – accessible within the contract only.
Let’s achieve the same in Solana, shall we?
Public functions
All the functions we have defined since day1 till date are all public functions:
pubfnmy_public_function(ctx: Context<Initialize>) ->Result<()> {
// Function logic...Ok(())
}
Adding pub keyword prior to function declaration makes the function public.
You cannot remove the pub keyword for functions inside of the module (mod) labeled #[program]. It will not compile.
Don’t worry too much about the distinction between external and public
It’s generally inconvenient for a Solana program to call it’s own public function. If there is a pub function in a Solana program, for all practical purposes you can think of it as external in the context of Solidity.
If you want to call a public function inside the same Solana program, it’s much easier to wrap the public function with an internal implementation function and call that.
Private and Internal Functions
Although you cannot declare functions without pub inside the module with the #[program] macro, you can declare functions inside the file. Consider the following code:
use anchor_lang::prelude::*;
declare_id!("F26bvRaY1ut3TD1NhrXMsKHpssxF2PAUQ7SjZtnrLkaM");
#[program]pubmod func_test {
use super::*;
pubfninitialize(ctx: Context<Initialize>) ->Result<()> {
// -------- calling a "private" function --------letu = get_a_num();
msg!("{}", u);
Ok(())
}
}
// ------- We declared a non pub function over here -------fnget_a_num() ->u64 {
2
}
#[derive(Accounts)]pubstructInitialize {}
This will run and log as expected.
This is all you really need to know about public and internal functions if you want to build simple Solana programs, but if you want to organize your code better than just declaring a bunch of functions in the file outside the program, you can keep going. Rust, and hence Solana, does not have “classes” the way Solidity does, as Rust is not object-oriented. Hence, the distinction between “private” and “internal” is doesn’t have a straightforward analog to Rust.
Rust uses modules to organize code. The visibility of functions inside and outside these modules is well discussed in the Visibility and Privacy section of the Rust docs, but we will add our own Solana-oriented take below.
Internal function
This can be achieved by defining the function within the program module and making sure it is accessible within its own module and other modules where it is imported or used. Let’s see how to do that:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfninitialize(_ctx: Context<Initialize>) ->Result<()> {
// Call the internal_function from within its parent module
some_internal_function::internal_function();
Ok(())
}
pubmod some_internal_function {
pubfninternal_function() {
// Internal function logic...
}
}
}
mod do_something {
// Import func_visibility moduleuse crate::func_visibility;
pubfnsome_func_here() {
// Call the internal_function from outside its parent module
func_visibility::some_internal_function::internal_function();
// Do something else...
}
}
#[derive(Accounts)]pubstructInitialize {}
After building the program, if you navigate to the ./target/idl/func_visibility.json file, you will observe that the function defined within the some_internal_function module was not included in the built program. This indicates that the function some_internal_function is internal and can only be accessed within the program itself and any programs that import or use it.
From the example above, we were able to access internal_function function from within its “parent” module (func_visibility) and also from a separate module (do_something) outside the func_visibility module.
Private function
Defining a function within a specific module and ensuring they are not exposed outside that scope is a way to achieve private visibility:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfninitialize(_ctx: Context<Initialize>) ->Result<()> {
// Call the private_function from within its parent module
some_function_function::private_function();
Ok(())
}
pubmod some_function_function {
pub(in crate::func_visibility) fnprivate_function() {
// Private function logic...
}
}
}
#[derive(Accounts)]pubstructInitialize {}
The pub(in crate::func_visibility) keyword indicates that private_function function is only visible within func_visibility module.
We were able to call private_function successfully in the initialize function because the initialize function is within func_visibility module. Let’s try to call private_function from outside the module:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfninitialize(_ctx: Context<Initialize>) ->Result<()> {
// Call the private_function from within its parent module
some_private_function::private_function();
Ok(())
}
pubmod some_private_function {
pub(in crate::func_visibility) fnprivate_function() {
// Private function logic...
}
}
}
mod do_something {
// Import func_visibility moduleuse crate::func_visibility;
pubfnsome_func_here() {
// Call the private_function from outside its parent module
func_visibility::some_private_function::private_function()
// Do something...
}
}
#[derive(Accounts)]pubstructInitialize {}
Build the program. What happened? We got an error:
❌ error[E0624]: associated function private_function is private
This shows that private_function is not publicly accessible and can not be invoked from outside the module where it is visible. Check out visibility and privacy in Rust docs for more the pub visibility keyword.
Contract Inheritance
Direct translation of Solidity contract inheritance to Solana is not possible because Rust does not have classes.
However, a workaround in Rust involves creating separate modules that define specific functionality and then use those modules within our main program, thereby achieving something similar to Solidity’s contract inheritance.
Getting modules from another file
As programs get larger, we generally don’t want to put everything into one file. Here’s how we can organize logic into multiple files.
Let’s create another file in the src folder called calculate.rs and copy the provided code into it.
pubfnadd(x: u64, y: u64) ->u64 {
// Return the sum of x and y
x + y
}
This add function returns the sum of x and y.
And this, into lib.rs.
use anchor_lang::prelude::*;
// Import `calculate` module or cratepubmod calculate;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfnadd_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) ->Result<()> {
// Call `add` function in calculate.rsletresult = calculate::add(x, y);
msg!("{} + {} = {}", x, y, result);
Ok(())
}
}
#[derive(Accounts)]pubstructInitialize {}
In the program above, we imported the calculate module that was created earlier and declared a function called add_two_numbers that adds two numbers and logs the result. The add_two_numbers function calls the add function in the calculate module, passing x and y as arguments, then stores the return value in the result variable. The msg! macro logs the two numbers that was added and the result.
Modules don’t have to be separate files
The follow example declares a module inside lib.rs instead of calculate.rs.
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfnadd_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) ->Result<()> {
// Call `add` function in calculate.rsletresult = calculate::add(x, y);
msg!("{} + {} = {}", x, y, result);
Ok(())
}
}
mod calculate {
pubfnadd(x: u64, y: u64) ->u64 {
// Return the summation of x and y
x + y
}
}
#[derive(Accounts)]pubstructInitialize {}
This program does the same as the previous example, with the only difference being that the add function is present in the lib.rs file and within the calculate module. Also, adding the pub keyword to a function is crucial, as it makes the function publicly accessible. The code below won’t compile:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]pubmod func_visibility {
use super::*;
pubfninitialize(_ctx: Context<Initialize>) ->Result<()> {
// Call the private-like functionletresult2 = 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 itfnsome_func_here() ->u64 {
// Do something...return20;
}
}
#[derive(Accounts)]pubstructInitialize {}
Summary
In Solidity, we think a lot about function visibility because it’s very critical. Here’s how to think about it in Rust:
Public / External Functions: These are functions accessible both within and outside the program. In Solana, all functions declared are, by default, public. Everything in the #[program] block must be declared pub.
Internal Functions: These are functions accessible within the program itself and programs that inherit it. Functions inside a nested pub mod block are not included in the built program, but still, they can be accessed within or outside the parent module.
Private Functions: These are functions that are not publicly accessible and cannot be invoked from outside their module. Achieving private visibility in Rust/Solana involves defining a function within a specific module with the pub(in crate::<module>) keyword, which makes the function visible within just the module it was defined in.
Solidity achieves contract inheritance through classes, a feature that Rust, the language used in Solana, does not have. Nevertheless, you can still organize your code using Rust modules.
Cross Program Invocation In Anchor Cross Program Invocation (CPI) is Solana’s terminology for a program calling the public function of another program. We’ve already done CPI before when we sent a transfer SOL transaction to the system program. Here is the relevant snippet by way of reminder: pub fn send_sol(ctx: Context<SendSol>, amount: u64) -> Result<()> […]
Reading Another Anchor Program’s Account Data On Chain In Solidity, reading another contract’s storage requires calling a view function or the storage variable being public. In Solana, an off-chain client can read a storage account directly. This tutorial shows how an on-chain Solana program can read the data in an account it does not own. […]
[derive(Accounts)] in Anchor: different kinds of accounts #[derive(Accounts)] in Solana Anchor is an attribute-like macro for structs that holds references to all the accounts the function will access during its execution. In Solana, every account the transaction will access must be specified in advance One reason Solana is so fast is that it executes transactions […]
Modifying accounts using different signers In our Solana tutorials thus far, we’ve only had one account initialize and write to the account. In practice, this is very restrictive. For example, if user Alice is transferring points to Bob, Alice must be able to write to an account initialized by user Bob. In this tutorial we […]
team-rareskills
1 year ago
11 mins read
Featured Jobs
RareSkills Researcher
As a RareSkills researcher, you will be contributing to the technical content we post on our website.