Developing Programs in Rust
Solana programs are primarily developed using the Rust programming language. This page focuses on writing Solana programs in Rust without using the Anchor framework, an approach often referred to as writing "native Rust" programs.
Native Rust development provides developers with direct control over their Solana programs. However, this approach requires more manual setup and boilerplate code compared to using the Anchor framework. This method is recommended for developers who:
- Seek granular control over program logic and optimizations
- Want to learn the underlying concepts before moving to higher-level frameworks
For beginners, we recommend starting with the Anchor framework. See the Anchor section for more information.
Prerequisites
For detailed installation instructions, visit the installation page.
Before you begin, ensure you have the following installed:
- Rust: The programming language for building Solana programs.
- Solana CLI: Command-line tool for Solana development.
Getting Started
The example below covers the basic steps to create your first Solana program written in Rust. We'll create a minimal program that prints "Hello, world!" to the program log.
Create a new program
First, create a new Rust project using the standard cargo new
command with the
--lib
flag.
$cargo new hello_world --lib
Navigate to the project directory. You should see the default src/lib.rs
and
Cargo.toml
files
$cd hello_world
Update the edition
field in Cargo.toml
to 2021
. Otherwise, you might
encounter an error when building the program.
Add the solana-program dependency
Next, add the solana-program
dependency. This is the minimum dependency
required to build a Solana program.
$cargo add solana-program@2.2.0
Add the crate-type
Next, add the following snippet to Cargo.toml
.
[lib]crate-type = ["cdylib", "lib"]
If you don't include this config, the target/deploy
directory will not be
generated when you build the program.
Your Cargo.toml
file should look like the following:
[package]name = "hello_world"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib", "lib"][dependencies]solana-program = "2.2.0"
Add the program code
Next, replace the contents of src/lib.rs
with the following code. This is a
minimal Solana program that prints "Hello, world!" to the program log when the
program is invoked.
The msg!
macro is used in Solana programs to print a message to the program
log.
use solana_program::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,};entrypoint!(process_instruction);pub fn process_instruction(_program_id: &Pubkey,_accounts: &[AccountInfo],_instruction_data: &[u8],) -> ProgramResult {msg!("Hello, world!");Ok(())}
Build the program
Next, build the program using the cargo build-sbf
command.
$cargo build-sbf
This command generates a target/deploy
directory containing two important
files:
- A
.so
file (e.g.,hello_world.so
): This is the compiled Solana program that will be deployed to the network as a "smart contract". - A keypair file (e.g.,
hello_world-keypair.json
): The public key of this keypair is used as the program ID when deploying the program.
To view the program ID, run the following command in your terminal. This command prints the public key of the keypair at the specified file path:
$solana address -k ./target/deploy/hello_world-keypair.json
Example output:
4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz
Add test dependencies
Next, test the program using the litesvm
crate. Add the following dependencies
to Cargo.toml
.
$cargo add litesvm --dev$cargo add solana-sdk@2.2.0 --dev
Test the program
Add the following test to src/lib.rs
, below the program code. This is a test
module that invokes the hello world program.
use solana_program::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,};entrypoint!(process_instruction);pub fn process_instruction(_program_id: &Pubkey,_accounts: &[AccountInfo],_instruction_data: &[u8],) -> ProgramResult {msg!("Hello, world!");Ok(())}#[cfg(test)]mod test {use litesvm::LiteSVM;use solana_sdk::{instruction::Instruction,message::Message,signature::{Keypair, Signer},transaction::Transaction,};#[test]fn test_hello_world() {// Create a new LiteSVM instancelet mut svm = LiteSVM::new();// Create a keypair for the transaction payerlet payer = Keypair::new();// Airdrop some lamports to the payersvm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();// Load our programlet program_keypair = Keypair::new();let program_id = program_keypair.pubkey();svm.add_program_from_file(program_id, "target/deploy/hello_world.so").unwrap();// Create instruction with no accounts and no datalet instruction = Instruction {program_id,accounts: vec![],data: vec![],};// Create transactionlet message = Message::new(&[instruction], Some(&payer.pubkey()));let transaction = Transaction::new(&[&payer], message, svm.latest_blockhash());// Send transaction and verify it succeedslet result = svm.send_transaction(transaction);assert!(result.is_ok(), "Transaction should succeed");let logs = result.unwrap().logs;println!("Logs: {logs:#?}");}}
Run the test using the cargo test
command. The program log will display
"Hello, world!".
$cargo test -- --no-capture
Example output:
running 1 testLogs: ["Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk invoke [1]","Program log: Hello, world!","Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk consumed 211 of 200000 compute units","Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk success",]test test::test_hello_world ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
Deploy the program
Next, deploy the program. When developing locally, we can use the
solana-test-validator
.
First, configure the Solana CLI to use the local Solana cluster.
$solana config set -ul
Example output:
Config File: /.config/solana/cli/config.ymlRPC URL: http://localhost:8899WebSocket URL: ws://localhost:8900/ (computed)Keypair Path: /.config/solana/id.jsonCommitment: confirmed
Open a new terminal and run the solana-test-validators
command to start the
local validator.
$solana-test-validator
While the test validator is running, run the solana program deploy
command in
a separate terminal to deploy the program to the local validator.
$solana program deploy ./target/deploy/hello_world.so
Example output:
Program Id: 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMzSignature:5osMiNMiDZGM7L1e2tPHxU8wdB8gwG8fDnXLg5G7SbhwFz4dHshYgAijk4wSQL5cXiu8z1MMou5kLadAQuHp7ybH
You can inspect the program ID and transaction signature on Solana Explorer.
Note that the cluster on Solana Explorer must also be localhost. The "Custom
RPC URL" option on Solana Explorer defaults to http://localhost:8899
.
Create example client
Next, we'll demonstrate how to invoke the program using a Rust client.
First create an examples
directory and a client.rs
file.
$mkdir -p examples && touch examples/client.rs
Add the following to Cargo.toml
.
[[example]]name = "client"path = "examples/client.rs"
Add solana-client
and tokio
dependencies.
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
Add the client
Add the following code to examples/client.rs
. This is a Rust client script
that funds a new keypair to pay for transaction fees and then invokes the hello
world program.
use solana_client::rpc_client::RpcClient;use solana_sdk::{commitment_config::CommitmentConfig,instruction::Instruction,pubkey::Pubkey,signature::{Keypair, Signer},transaction::Transaction,};use std::str::FromStr;#[tokio::main]async fn main() {// Program ID (replace with your actual program ID)let program_id = Pubkey::from_str("4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz").unwrap();// Connect to the Solana devnetlet rpc_url = String::from("http://localhost:8899");let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());// Generate a new keypair for the payerlet payer = Keypair::new();// Request airdroplet airdrop_amount = 1_000_000_000; // 1 SOLlet signature = client.request_airdrop(&payer.pubkey(), airdrop_amount).expect("Failed to request airdrop");// Wait for airdrop confirmationloop {let confirmed = client.confirm_transaction(&signature).unwrap();if confirmed {break;}}// Create the instructionlet instruction = Instruction::new_with_borsh(program_id,&(), // Empty instruction datavec![], // No accounts needed);// Add the instruction to new transactionlet mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));transaction.sign(&[&payer], client.get_latest_blockhash().unwrap());// Send and confirm the transactionmatch client.send_and_confirm_transaction(&transaction) {Ok(signature) => println!("Transaction Signature: {}", signature),Err(err) => eprintln!("Error sending transaction: {}", err),}}
Replace the program ID
Before running the client code, replace the program ID in the code snippet with the one for your program.
You can get your program ID by running the following command.
$solana address -k ./target/deploy/hello_world-keypair.json
Invoke the program
Run the client script with the following command.
$cargo run --example client
Example output:
Transaction Signature: 54TWxKi3Jsi3UTeZbhLGUFX6JQH7TspRJjRRFZ8NFnwG5BXM9udxiX77bAACjKAS9fGnVeEazrXL4SfKrW7xZFYV
You can inspect the transaction signature on Solana Explorer (local cluster) to see "Hello, world!" in the program log.
Update the program
Solana programs can be updated by redeploying to the same program ID. Update the
program in src/lib.rs
to print "Hello, Solana!" instead of "Hello, world!".
pub fn process_instruction(_program_id: &Pubkey,_accounts: &[AccountInfo],_instruction_data: &[u8],) -> ProgramResult {-msg!("Hello, world!");+msg!("Hello, Solana!");Ok(())}
Run the cargo build-sbf
command to generate an updated .so
file.
$cargo build-sbf
Test the updated program by running the cargo test
command.
$cargo test -- --no-capture
You should see "Hello, Solana!" in the program log.
running 1 testLogs: ["Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV invoke [1]","Program log: Hello, Solana!","Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV consumed 211 of 200000 compute units","Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV success",]test test::test_hello_world ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
Redeploy the program
Redeploy the program using the same solana program deploy
command.
$solana program deploy ./target/deploy/hello_world.so
Run the client code again and inspect the transaction signature on Solana Explorer to see "Hello, Solana!" in the program log.
$cargo run --example client
Close the program
You can close your Solana program to reclaim the SOL allocated to the account. Closing a program is irreversible, so it should be done with caution.
To close a program, use the solana program close <PROGRAM_ID>
command. For
example:
$solana program close 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz --bypass-warning
Example output:
Closed Program Id 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz, 0.1350588 SOLreclaimed
Note that once a program is closed, its program ID cannot be reused. Attempting to deploy a program with a previously closed program ID will result in an error.
Error: Program 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz has been closed, usea new Program Id
Redeploy a closed program
If you need to redeploy a program with the same source code after closing a program, you must generate a new program ID. To generate a new keypair for the program, run the following command:
$solana-keygen new -o ./target/deploy/hello_world-keypair.json --force
Alternatively, you can delete the existing keypair file (e.g.
./target/deploy/hello_world-keypair.json
) and run cargo build-sbf
again,
which will generate a new keypair file.
Is this page helpful?