Leo Libraries
A library is a Leo project that contains reusable code — structs, constants, and helper functions — intended to be shared across multiple programs. Unlike a regular Leo program, a library has no on-chain footprint: it declares no program ID, no mappings, no records, and no entry functions. All library code is inlined into the programs that use it at compile time.
Every Leo program implicitly depends on one built-in library: std. See the Standard Library reference for the full catalog of hash, commit, signature, randomness, serialization, group, and execution-context functions available without any declaration.
Creating a Library
Use leo new with the --library flag to create a library project:
leo new math_utils --library
This produces the same project structure as a regular Leo project, with one difference: the main source file is named lib.leo instead of main.leo.
math_utils/
├── program.json
├── src/
│ └── lib.leo
└── tests/
└── test_math_utils.leo
Writing a Library
A library source file (lib.leo) may contain struct definitions, const declarations, and fn definitions. It does not contain a program { } block.
/// The maximum value representable by a u32.
export const MAX_U32: u32 = 4294967295u32;
/// A 2-D point with integer coordinates.
export struct Point {
x: i32,
y: i32,
}
/// Returns the absolute value of a signed 32-bit integer.
export fn abs(x: i32) -> i32 {
return x >= 0i32 ? x : 0i32 - x;
}
/// Returns the Manhattan distance between two points.
export fn manhattan(a: Point, b: Point) -> u32 {
let dx: i32 = abs(a.x - b.x);
let dy: i32 = abs(a.y - b.y);
return dx as u32 + dy as u32;
}
By default, a library item is private to its source file. Add export to an item that another source file or package must use. Other declarations in the same file can use the private item.
What a library may contain
| Item | Allowed | Notes |
|---|---|---|
const declarations | ✅ | Global compile-time constants |
struct definitions | ✅ | Shared data types |
fn definitions | ✅ | Helper functions, including generic fn::[…] |
program { } block | ❌ | Libraries have no on-chain identity |
mapping / storage | ❌ | No on-chain state |
record types | ❌ | Records belong to programs |
Entry fn / final fn | ❌ | No callable entry points |
Declaring the Dependency
To use a library from another Leo project, add it to that project's program.json. Libraries can only be referenced from the local filesystem.
Local library
{
"program": "my_app.aleo",
"version": "0.1.0",
"description": "",
"license": "MIT",
"dependencies": [
{
"name": "math_utils",
"location": "local",
"path": "../math_utils",
"edition": null
}
]
}
The leo add command can populate these entries automatically:
leo add math_utils --local ../math_utils
Using a Library
Use the {library_name}::{item} path syntax to refer to exported library items. You do not need an import statement. The dependency entry in program.json is sufficient.
program my_app.aleo {
fn closest(
origin: math_utils::Point,
a: math_utils::Point,
b: math_utils::Point,
) -> math_utils::Point {
let da: u32 = math_utils::manhattan(origin, a);
let db: u32 = math_utils::manhattan(origin, b);
return da <= db ? a : b;
}
@noupgrade
constructor() {}
}
Constants from a library are referenced the same way:
const CEILING: u32 = math_utils::MAX_U32;
Generic Library Functions
Library functions support const generic parameters, just like regular helper functions. The concrete type argument must be a compile-time constant.
/// Clamps `value` to the range [0, MAX].
export fn clamp::[MAX: u32](value: u32) -> u32 {
return value > MAX ? MAX : value;
}
program my_app.aleo {
fn normalize(x: u32) -> u32 {
return math_utils::clamp::[100u32](x);
}
@noupgrade
constructor() {}
}
Const-generic library functions operate like const-generic functions in a program. The compiler monomorphizes each library::fn::[const_args](runtime_args) call for its const arguments. Then, it puts the function code in the caller. Library code always uses this process, including code across package boundaries. A consuming program can reference and instantiate const-generic library structs with a fully qualified path. For example, use math_utils::Vec::[10].
Submodules
A library can have multiple source files. Put additional .leo files with lib.leo in src/ to create submodules. Each file creates a submodule that has the file name. Export an item before you access it through the additional path segment.
math_utils/
├── src/
│ ├── lib.leo ← root: math_utils::item
│ └── geometry.leo ← submodule: math_utils::geometry::item
export fn area(width: u32, height: u32) -> u32 {
return width * height;
}
program my_app.aleo {
fn floor_area(w: u32, h: u32) -> u32 {
return math_utils::geometry::area(w, h);
}
@noupgrade
constructor() {}
}
Name Resolution and Path Precedence
When a library dependency and local submodule have the same name, paths with that name resolve to the library first. For example, assume that a library dependency is named foo and a local submodule is src/foo.leo. In this case, foo::bar refers to bar in the library, not the submodule.
Explicit disambiguation using absolute paths (similar to Rust's crate::foo::… for local modules) is planned for a future release.
Building a Library
Running leo build inside a library package parses the library sources and runs semantic validation on the library itself. Type errors, unknown identifiers, interface-cycle errors, and the like are reported at the library package, instead of surfacing only when a downstream program consumes it.
cd math_utils
leo build
Leo 🔨 Building library 'math_utils'
Leo ✅ Validated 'math_utils'.
The build does not produce bytecode because libraries have no on-chain footprint. However, Leo reports frontend errors with spans that point to the library source files.
Leo compiles library sources with each program that depends on the library. Thus, the consuming program build reports errors in the library. Run leo build in the library package to validate it separately. This operation finds problems before a consumer uses the library.
Testing
leo test works on library packages directly — no wrapper program is required. Place test files in the tests/ directory and call library functions using the library_name::item path syntax.
program test_math_utils.aleo {
@test
fn test_abs() {
assert_eq(math_utils::abs(0i32 - 5i32), 5i32);
}
@test
fn test_geometry_area() {
assert_eq(math_utils::geometry::area(3u32, 4u32), 12u32);
}
@noupgrade
constructor() {}
}
Run from the library root:
leo test
See the Testing guide for more details.
How Libraries Work
Libraries are inlined at compile time. The Leo compiler resolves all library references before emitting Aleo bytecode — no library code appears as a separate program on-chain. This means:
- Calling a library function has the same cost as calling an inline helper function.
- Libraries cannot be deployed independently. They exist only as source-level abstractions.
- Circular dependencies between libraries are not allowed.
For more on how dependencies are resolved and cached, see Dependency Management.