Libraries Guide
Create, distribute, and use reusable Strada libraries.
Overview
Strada supports separate compilation: each library is precompiled once, then linked into programs that use it — so a large project doesn't recompile the world on every build.
There are three artifact types, all picked up automatically by a plain use Foo;:
Object Files (.o) — default
Built with strada -M Foo.strada. Statically linked. With --use-artifacts, use Foo; auto-detects and links a sibling Foo.o instead of re-inlining the source (off by default — see note below).
Best for: normal library distribution, fast incremental builds
Shared Libraries (.so)
Built with strada --shared Foo.strada. Loaded at runtime via dlopen; with --use-artifacts, use Foo; auto-detects a fresh sibling Foo.so too. Explicit form: import_lib (always works).
Best for: plugins, runtime-optional features, FFI-style use
Archive Files (.a)
Built with strada --static-lib Foo.strada. Static archive that includes the Strada runtime. Explicit form: import_archive.
Best for: self-contained distribution that doesn't depend on the runtime being installed
--use-artifacts): silently linking a precompiled Foo.o/Foo.so runs that artifact’s code at compile time, so by default a plain use Foo; ignores sibling artifacts and recompiles from Foo.strada. Pass --use-artifacts to opt in: use Foo; then links a fresh sibling Foo.o/Foo.so (mtime ≥ source) instead of re-parsing, falling back to source if the artifact is stale. The explicit import_lib/import_object/import_archive forms always link the artifact you name and are unaffected by the flag.
Creating a Library
Step 1: Write Your Library
Create a Strada file with a package declaration and optional version:
# lib/MathLib.strada
package MathLib;
version "1.0.0";
func add(int $a, int $b) int {
return $a + $b;
}
func multiply(int $a, int $b) int {
return $a * $b;
}
func factorial(int $n) int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
func add() becomes MathLib_add() internally.
::func() shorthand:
func compute(int $x, int $y) int {
return ::add($x, $y) * ::multiply($x, $y);
}
Step 2: Compile the Library
Module-only object file (recommended, default):
./strada -M lib/MathLib.strada
# -> lib/MathLib.o (only MathLib's own symbols; consumers `use MathLib;`)
Recursive build of a whole tree:
./strada -M src/
# -> sibling .o next to every .strada under src/
Bundled object file (legacy; embeds all used module code):
./strada --object-full lib/MathLib.strada -o lib/MathLib.o
Shared library:
./strada --shared lib/MathLib.strada -o lib/MathLib.so
Static archive (.a) — includes runtime:
./strada --static-lib lib/MathLib.strada -o lib/MathLib.a
./configure and make libs to build the standard libraries (DBI, crypt, ssl, readline) with auto-detected dependencies:
./configure # Detect MySQL, PostgreSQL, OpenSSL, etc.
make libs # Build all libraries
.so, .o, and .a files via __strada_export_info(). You don't need to keep the original .strada source file.
--static-lib include the Strada runtime, making them fully self-contained. The compiler automatically skips the runtime when linking against archives to avoid duplicate symbols.
Using a Library
Just use Foo;. The compiler looks for Foo.strada in the lib paths, then checks for a fresh sibling Foo.o or Foo.so. If one exists (mtime ≥ source), it's linked in automatically and the source is not re-parsed. Falls back to source inlining if neither artifact is fresh.
use lib "lib";
use MathLib; # picks up lib/MathLib.o (or .so) if present
func main() int {
my int $sum = MathLib::add(10, 32);
say($sum);
return 0;
}
You can also pass .o or .a files directly on the CLI; the driver uses nm to tell Strada modules apart from plain extern-C objects, and forwards the Strada ones to the compiler as implicit imports:
./strada main.strada lib/MathLib.o helper.o
That covers nearly all everyday usage. The explicit import_lib, import_object, and import_archive statements below are still supported — useful when the artifact isn't shipped next to a .strada source, or when you need a specific form regardless of what's on disk.
Declaring Extern-C Link Dependencies (link_lib)
A Strada library that wraps a C library (say, libsqlite3) needs the consumer to pass -lsqlite3 to the final link. Rather than putting that burden on every caller, declare it in source with link_lib:
package DBI;
#ifdef HAVE_SQLITE3
link_lib "sqlite3";
#endif
#ifdef HAVE_MYSQL
link_lib "mysqlclient";
#endif
# ... rest of DBI ...
The link_lib strings flow through __strada_export_info and into __STRADA_LINK_LIBS__ metadata in the produced .o. When another program says use DBI;, the strada driver sees the metadata and adds -lsqlite3 -lmysqlclient to gcc automatically. Multiple link_lib calls accumulate and dedupe.
Using Shared Libraries (import_lib)
Shared libraries are loaded at runtime using dlopen(). They're loaded lazily on first function call.
Basic Usage
use lib "lib"; # Add lib/ to search path
import_lib "MathLib.so"; # Import MathLib.so
func main() int {
# Call with namespace syntax (recommended)
my int $sum = MathLib::add(10, 32);
my int $product = MathLib::multiply(6, 7);
my int $fact = MathLib::factorial(5);
say("Sum: " . $sum); # 42
say("Product: " . $product); # 42
say("Factorial: " . $fact); # 120
return 0;
}
Requirements
| File | Purpose | Required |
|---|---|---|
LibName.so | Compiled code + embedded metadata | Yes |
LibName.strada | Source (only needed for recompiling) | No |
How It Works
- At compile time: Compiler loads
MathLib.soand calls__strada_export_info()to extract function signatures - Compiler generates wrapper functions that call into the shared library
- At runtime: First call to any library function loads
MathLib.soviadlopen() - Subsequent calls use cached function pointers
Using Object Files (import_object)
Object files are linked directly into your executable at compile time. The result is a single, self-contained binary.
Basic Usage
use lib "lib"; # Add lib/ to search path
import_object "MathLib.o"; # Link MathLib.o statically
func main() int {
# Same syntax as import_lib
my int $sum = MathLib::add(10, 32);
say($sum); # 42
return 0;
}
Requirements
| File | Purpose | Required |
|---|---|---|
LibName.o | Compiled code + embedded metadata | Yes |
LibName.strada | Source (only needed for recompiling) | No |
How It Works
- At compile time: Compiler extracts metadata from
MathLib.oby calling__strada_export_info() - Generates direct function calls (no wrappers needed)
- Links
MathLib.ointo the final executable with gcc
Using Archive Files (import_archive)
Archive files are self-contained static libraries that include the Strada runtime. They're ideal for distributing libraries without requiring the runtime separately.
Basic Usage
use lib "lib"; # Add lib/ to search path
import_archive "MathLib.a"; # Link MathLib.a statically
func main() int {
# Same syntax as import_lib and import_object
my int $sum = MathLib::add(10, 32);
say($sum); # 42
return 0;
}
Requirements
| File | Purpose | Required |
|---|---|---|
LibName.a | Compiled code + runtime + embedded metadata | Yes |
LibName.strada | Source (only needed for recompiling) | No |
How It Works
- At compile time: Compiler extracts metadata from
MathLib.aby calling__strada_export_info() - Generates direct function calls (no wrappers needed)
- Links
MathLib.ainto the final executable - Skips runtime linking since the archive includes it
Creating Archive Files
# Create archive with bundled runtime
./strada --static-lib lib/MathLib.strada
mv MathLib.a lib/
--static-lib include the Strada runtime (strada_runtime.o). When you import an archive, the compiler automatically skips linking the runtime separately to avoid duplicate symbols.
Comparing Library Types
| Feature | Shared (.so) | Object (.o) | Archive (.a) |
|---|---|---|---|
| Linking | Runtime (dlopen) | Compile time | Compile time |
| Distribution | Multiple files | Single executable | Single executable |
| Includes runtime | No | No | Yes |
| Startup time | Slightly slower (lazy load) | Faster | Faster |
| Memory sharing | Shared between processes | Per-process | Per-process |
| Updates | Replace .so without recompile | Requires recompile | Requires recompile |
| Plugins | Excellent | Not suitable | Not suitable |
| Self-contained | No (needs runtime) | No (needs runtime) | Yes |
- Use
import_libfor optional features or plugins - Use
import_objectfor core libraries you always need - Use
import_archivefor distributing self-contained libraries to users who don't have the Strada runtime
Library Search Paths
The compiler searches for libraries in this order:
- Current directory
- Paths added with
use lib "path"(in order) - Paths added with
-L pathflag (high priority) - Paths added with
-LL pathflag (low priority)
# Multiple search paths
use lib "lib";
use lib "vendor/lib";
use lib "/usr/local/strada/lib";
import_lib "JSON.so"; # Searches all paths for JSON.so
import_object "Utils.o"; # Searches all paths for Utils.o
import_archive "DataLib.a"; # Searches all paths for DataLib.a
Library Versioning
Add a version to your library with the version statement:
package JSON;
version "2.1.0";
func encode(scalar $data) str {
# ...
}
Retrieve the version at runtime:
my int $lib = core::dl_open("lib/JSON.so");
my int $fn = core::dl_sym($lib, "__strada_version");
my str $version = core::dl_call_version($fn);
say("JSON version: " . $version); # "2.1.0"
Inspecting Libraries
Use the soinfo tool to inspect compiled libraries:
# View library information
./tools/soinfo lib/JSON.so
# Show usage examples
./tools/soinfo --examples lib/JSON.so
Example output:
Library: lib/JSON.so
Package: JSON
Version: 2.1.0
Exported functions:
JSON_encode(scalar $data) -> str
JSON_decode(str $json) -> scalar
JSON_pretty(scalar $data) -> str
Call examples:
my str $result = JSON::encode($data);
my scalar $result = JSON::decode($json);
my str $result = JSON::pretty($data);
Complete Example: JSON Library
1. The Bundled JSON Module
Strada ships lib/JSON.strada — a pure-Strada JSON
implementation (no special builtins required). Its public API:
# lib/JSON.strada (bundled with Strada)
package JSON;
func encode(scalar $data) str { /* value -> JSON text */ }
func decode(str $json) scalar { /* JSON text -> value */ }
func encode_opts(scalar $data, scalar $opts) str { /* with options */ }
2. Compile
# As shared library (runtime loading)
./strada --shared lib/JSON.strada && mv JSON.so lib/
# Or as object file (static linking)
./strada --object lib/JSON.strada && mv JSON.o lib/
# Or as archive file (static linking with bundled runtime)
./strada --static-lib lib/JSON.strada && mv JSON.a lib/
3. Use in Your Program
# app.strada
use JSON; # the bundled module
func main() int {
# Create some data
my hash %user = {
"name" => "Alice",
"age" => 30,
"roles" => ["admin", "user"]
};
# Encode to JSON
my str $json = JSON::encode(\%user);
say($json);
# {"name":"Alice","age":30,"roles":["admin","user"]}
# Decode back
my scalar $decoded = JSON::decode($json);
say($decoded->{"name"}); # Alice
return 0;
}
4. Compile and Run
./strada app.strada
./app
Libraries with C Code
Libraries can include embedded C code using __C__ blocks:
# lib/Compress.strada
package Compress;
version "1.0.0";
__C__ {
#include <zlib.h>
}
func gzip(str $data) str {
__C__ {
char *input = strada_to_str(data);
size_t input_len = strlen(input);
size_t output_len = compressBound(input_len);
char *output = malloc(output_len);
compress((Bytef*)output, &output_len,
(Bytef*)input, input_len);
free(input);
StradaValue *result = strada_new_str_len(output, output_len);
free(output);
return result;
}
}
Compile with additional libraries:
./strada --shared lib/Compress.strada -- -lz
Standard Libraries
Strada includes several standard libraries that require external dependencies. Use ./configure and make libs to build them with auto-detected dependencies.
DBI (Database Interface)
The DBI library provides a unified interface for MySQL, SQLite, and PostgreSQL. The configure script detects which database drivers are available.
# Install database development libraries
sudo apt install libmysqlclient-dev # MySQL
sudo apt install libsqlite3-dev # SQLite
sudo apt install libpq-dev # PostgreSQL
# Configure to detect databases
./configure
# Build DBI with detected drivers
make lib-dbi
# Output shows which drivers are enabled:
# DBI drivers: MySQL=1 SQLite=1 PostgreSQL=0
Other Standard Libraries
| Library | Make Target | Dependencies | Purpose |
|---|---|---|---|
lib/DBI.o | make lib-dbi | libmysqlclient, libsqlite3, libpq | Database interface |
lib/crypt.o | make lib-crypt | libcrypt | Password hashing |
lib/ssl.o | make lib-ssl | libssl, libcrypto | SSL/TLS support |
lib/readline/readline.so | make lib-readline | libreadline | Line editing for REPL |
# Build all standard libraries at once
make libs
Troubleshooting
Common Errors
| Error | Cause | Solution |
|---|---|---|
cannot find LibName.so |
.so file not in search path | Add use lib "path" or move .so to lib/ |
cannot find LibName.o |
.o file not in search path | Add use lib "path" or move .o to lib/ |
cannot find LibName.a |
.a file not in search path | Add use lib "path" or move .a to lib/ |
undefined symbol |
Function not exported | Check package name matches function prefix |
missing __strada_export_info |
Old library format | Recompile library with current compiler |
multiple definition |
Runtime linked twice with archive | This shouldn't happen - compiler auto-skips runtime for archives |