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

Sibling auto-detect is opt-in (--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);
}
Package naming: Functions are automatically prefixed with the package name. func add() becomes MathLib_add() internally.
Tip: Call other functions in the same package using ::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
Building standard libraries: Use ./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
Self-contained metadata: Function signatures are embedded in the compiled .so, .o, and .a files via __strada_export_info(). You don't need to keep the original .strada source file.
Archive includes runtime: Archives created with --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

FilePurposeRequired
LibName.soCompiled code + embedded metadataYes
LibName.stradaSource (only needed for recompiling)No

How It Works

  1. At compile time: Compiler loads MathLib.so and calls __strada_export_info() to extract function signatures
  2. Compiler generates wrapper functions that call into the shared library
  3. At runtime: First call to any library function loads MathLib.so via dlopen()
  4. 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

FilePurposeRequired
LibName.oCompiled code + embedded metadataYes
LibName.stradaSource (only needed for recompiling)No

How It Works

  1. At compile time: Compiler extracts metadata from MathLib.o by calling __strada_export_info()
  2. Generates direct function calls (no wrappers needed)
  3. Links MathLib.o into 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

FilePurposeRequired
LibName.aCompiled code + runtime + embedded metadataYes
LibName.stradaSource (only needed for recompiling)No

How It Works

  1. At compile time: Compiler extracts metadata from MathLib.a by calling __strada_export_info()
  2. Generates direct function calls (no wrappers needed)
  3. Links MathLib.a into the final executable
  4. 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/
Runtime bundling: Archives created with --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

FeatureShared (.so)Object (.o)Archive (.a)
LinkingRuntime (dlopen)Compile timeCompile time
DistributionMultiple filesSingle executableSingle executable
Includes runtimeNoNoYes
Startup timeSlightly slower (lazy load)FasterFaster
Memory sharingShared between processesPer-processPer-process
UpdatesReplace .so without recompileRequires recompileRequires recompile
PluginsExcellentNot suitableNot suitable
Self-containedNo (needs runtime)No (needs runtime)Yes
Rule of thumb:
  • Use import_lib for optional features or plugins
  • Use import_object for core libraries you always need
  • Use import_archive for distributing self-contained libraries to users who don't have the Strada runtime

Library Search Paths

The compiler searches for libraries in this order:

  1. Current directory
  2. Paths added with use lib "path" (in order)
  3. Paths added with -L path flag (high priority)
  4. Paths added with -LL path flag (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

LibraryMake TargetDependenciesPurpose
lib/DBI.omake lib-dbilibmysqlclient, libsqlite3, libpqDatabase interface
lib/crypt.omake lib-cryptlibcryptPassword hashing
lib/ssl.omake lib-ssllibssl, libcryptoSSL/TLS support
lib/readline/readline.somake lib-readlinelibreadlineLine editing for REPL
# Build all standard libraries at once
make libs

Troubleshooting

Common Errors

ErrorCauseSolution
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