Everything needs to be made
#![no_std]. No more role playing.
— Alessandro
Rust’s standard library assumes programs are running in a hosted environment, usually a full operating system.
Solana programs are not.
There is no filesystem. No sockets. No threads. No processes. No stdout.
Instead, the Solana virtual machine exposes a very small set of runtime-specific capabilities:
- account memory
- syscalls for logging
sol_log_, hashingsol_sha256, and cross-program invocationsol_invoke_signed_c.
Yet for years, Solana programs have been compiled in an environment pretending to provide Rust’s standard library.
The first rule of thumb when working with compilers is to be explicit about your assumptions.
So stop fooling the compiler into thinking we have an operating system.
WE DON’T.
What is no_std?
A normal Rust program implicitly links against Rust’s standard library aka std.
fn main() { println!("hello"); }
When you add,
#![no_std]
Rust stops providing std, but this does not mean you lose Rust.
You still have core: Option, Result, slices, iterators, traits, atomics, pointer operations, and most of the fundamental building blocks of the language.
The Solana virtual machine provides an allocator, so you can also use alloc, which gives you Vec, String, Box, and other heap-allocated types.
A useful way to think about Rust’s libraries is:
- core → Rust without runtime assumptions
- alloc → core + heap allocation
- std → core + alloc + hosted runtime capabilities
no_std was built for environments where those hosted runtime capabilities do not exist: embedded systems, operating system kernels, and other freestanding targets (sound familiar?).
no_std also catches bad dependencies early; it’s better to find out today than 200 dependencies deep tomorrow.
How to write no_std
If you’re a Solana program dev, learn what
no_stdRust is today. You will thank me tomorrow.
— Trent
The first step is almost disappointingly simple:
#![no_std]
Then compile your program.
And let the compiler tell you where your runtime assumptions are hiding.
A lot of std usage is trivial to remove.
use std::mem::MaybeUninit;
becomes:
use core::mem::MaybeUninit;
std::ptr becomes core::ptr.
std::slice becomes core::slice.
std::str becomes core::str.
These APIs never needed an operating system. std was simply re-exporting them from core.
Need a Vec?
extern crate alloc; use alloc::vec::Vec;
Need a String?
use alloc::string::String;
Most of the things you reach for in std either live in core, live in alloc, or have a no_std library designed for constrained runtimes.
And if you can’t find an equivalent?
Ask why.
Maybe it depends on a filesystem.
Maybe it needs threads.
Maybe it expects a clock.
Maybe it assumes a process environment.
And what did I just say?
WE DON’T HAVE AN OS.
HashMap
HashMap is a surprisingly good example.
You cannot write:
use alloc::collections::HashMap;
There is no HashMap in alloc.
Rust’s standard HashMap uses a randomly seeded hash builder by default. That design provides HashDoS resistance in normal applications, but random seeding assumes access to runtime entropy, which is not naturally provided by a tiny deterministic VM like Solana.
More importantly, ask whether you needed the hash map in the first place.
Consider building a map of accounts just to perform a few lookups:
let mut accounts_by_key = HashMap::new(); for account in accounts { accounts_by_key.insert(account.key, account); } let account = accounts_by_key.get(&key);
A hash map means allocation, hashing, probing, and potentially resizing.
For a small, bounded list of accounts:
let account = accounts .iter() .find(|account| account.key == &key);
may be simpler and cheaper.
no_std did not tell us that HashMap is bad.
It forced us to explain why we needed one.
Use protection, no_std
no_std is usually explained as “Rust for embedded systems.”
I think that’s underselling it.
no_std is a runtime boundary.
It prevents a dependency from silently assuming that files, sockets, threads, processes, clocks, or other runtime capabilities exist.
For Solana programs, those assumptions matter.
An on-chain program executes in one of the most constrained Rust environments in production today. Every allocation has a cost. Every instruction consumes compute. Persistent state is account memory, not a process heap. Runtime functionality comes from explicit syscalls.
Our Rust code should say that.
This article kicks off a new no_std era for Solana development.
We will continue the series based on feedback, dig into real Solana programs and dependencies, and see what happens when we stop role playing as an operating system.
Use protection.
#![no_std]
