C Interoperability

Embed C code directly in Strada programs and interface with C libraries.

Overview

Strada provides powerful C interoperability through __C__ blocks, which let you embed raw C code directly in your Strada programs. This enables:

Direct C Access

Call any C library function, use C data structures, and leverage existing C code.

High Performance

Write performance-critical code in C while keeping the rest of your program in Strada.

System Integration

Access operating system APIs, hardware interfaces, and low-level functionality.

__C__ Blocks

There are two types of __C__ blocks:

Top-Level Blocks

Place at file scope for includes, global variables, and C helper functions:

__C__ {
    #include <stdio.h>
    #include <math.h>
    #include <openssl/ssl.h>

    // Global state
    static SSL_CTX *g_ssl_ctx = NULL;
    static int g_initialized = 0;

    // C helper function
    static double fast_distance(double x1, double y1,
                                double x2, double y2) {
        double dx = x2 - x1;
        double dy = y2 - y1;
        return sqrt(dx*dx + dy*dy);
    }
}
Top-level blocks appear at the top of the generated C file, before any Strada-generated code. This ensures includes and declarations are available throughout the file.

Statement-Level Blocks

Use inside functions to write inline C code with access to Strada variables:

func calculate_sqrt(num $x) num {
    __C__ {
        // Access Strada variable 'x' (it's a StradaValue*)
        double val = strada_to_num(x);

        // Do the computation in C
        double result = sqrt(val);

        // Return a new StradaValue
        return strada_new_num(result);
    }
}
Variable Naming: Inside __C__ blocks, Strada variables are available by their name without the sigil. So $x becomes just x, and it's a StradaValue*.

Runtime Functions

The Strada runtime provides functions to convert between C types and StradaValue*:

Extracting Values (StradaValue* → C)

FunctionReturnsDescription
strada_to_int(sv)int64_tExtract integer value
strada_to_num(sv)doubleExtract floating-point value
strada_to_str(sv)char*Extract string (caller must free!)
strada_to_bool(sv)intExtract boolean (0 or 1)

Creating Values (C → StradaValue*)

FunctionParameterDescription
strada_new_int(i)int64_tCreate integer value
strada_new_num(n)doubleCreate floating-point value
strada_new_str(s)const char*Create string (copies the string)
strada_new_str_len(s, len)const char*, size_tCreate string with explicit length
&strada_undef-Return undef value

Reference Counting

FunctionDescription
strada_incref(sv)Increment reference count
strada_decref(sv)Decrement reference count (may free)
Memory Management: strada_to_str() allocates a new string that you must free(). Forgetting to free causes memory leaks!

Opaque Handle Pattern

C pointers can be stored in Strada int variables (which are 64-bit). This "opaque handle" pattern is the standard way to manage C resources:

__C__ {
    #include <openssl/ssl.h>

    static SSL_CTX *g_ctx = NULL;

    static void ensure_ssl_init() {
        if (!g_ctx) {
            SSL_library_init();
            g_ctx = SSL_CTX_new(TLS_client_method());
        }
    }
}

func ssl_connect(str $host, int $port) int {
    __C__ {
        ensure_ssl_init();

        char *h = strada_to_str(host);
        int p = (int)strada_to_int(port);

        // Create connection...
        SSL *ssl = SSL_new(g_ctx);
        // ... connect to host:port ...

        free(h);

        // Store pointer as int64_t
        return strada_new_int((int64_t)(intptr_t)ssl);
    }
}

func ssl_write(int $handle, str $data) int {
    __C__ {
        // Retrieve pointer from int
        SSL *ssl = (SSL*)(intptr_t)strada_to_int(handle);

        char *d = strada_to_str(data);
        int written = SSL_write(ssl, d, strlen(d));
        free(d);

        return strada_new_int(written);
    }
}

func ssl_close(int $handle) void {
    __C__ {
        SSL *ssl = (SSL*)(intptr_t)strada_to_int(handle);
        SSL_shutdown(ssl);
        SSL_free(ssl);
        return &strada_undef;
    }
}

Usage

my int $conn = ssl_connect("example.com", 443);
ssl_write($conn, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
# ... read response ...
ssl_close($conn);
Why int? Strada's int is 64-bit, large enough to hold any pointer on modern systems. The cast pattern (int64_t)(intptr_t)ptr is portable and safe.

Working with Strings

String handling requires careful memory management:

func uppercase(str $s) str {
    __C__ {
        // Extract string (allocates memory!)
        char *str = strada_to_str(s);

        // Modify in place
        for (char *p = str; *p; p++) {
            if (*p >= 'a' && *p <= 'z') {
                *p = *p - 32;
            }
        }

        // Create new StradaValue (copies the string)
        StradaValue *result = strada_new_str(str);

        // Free the temporary string
        free(str);

        return result;
    }
}

Binary Data

For binary data that may contain null bytes, use strada_new_str_len():

func read_binary(int $fd, int $len) str {
    __C__ {
        int fd = (int)strada_to_int(fd_arg);
        size_t len = (size_t)strada_to_int(len_arg);

        char *buf = malloc(len);
        ssize_t n = read(fd, buf, len);

        if (n <= 0) {
            free(buf);
            return &strada_undef;
        }

        // Preserve binary data including null bytes
        StradaValue *result = strada_new_str_len(buf, n);
        free(buf);
        return result;
    }
}

Shared Libraries

Creating a Shared Library

Compile your Strada code as a shared library:

# Create mylib.so from mylib.strada
./strada --shared mylib.strada

Your library can export functions that other Strada programs can call:

# 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;
}
# Compile to shared library
./strada --shared lib/MathLib.strada
mv MathLib.so lib/

Using import_lib

Import shared libraries with import_lib for clean namespace access:

use lib "lib";
import_lib "MathLib";

func main() int {
    my int $sum = MathLib::add(10, 32);
    my int $product = MathLib::multiply(6, 7);

    say("Sum: " . $sum);        # 42
    say("Product: " . $product); # 42

    return 0;
}
import_lib: Only LibName.so is required. Function signatures are embedded in the library via __strada_export_info().

Manual FFI

For more control, use the sys::dl_* functions directly:

my int $lib = sys::dl_open("./mylib.so");
my int $fn = sys::dl_sym($lib, "MathLib_add");
my scalar $result = sys::dl_call_sv($fn, [10, 32]);
say($result);  # 42
sys::dl_close($lib);

Extern Functions (C-Callable)

Use the extern keyword to create functions with C-compatible calling conventions. These functions can be called directly from C code without wrappers or type conversions.

Defining Extern Functions

# extern_lib.strada - Functions callable from C
package MathExtern;

# C signature: int64_t add_integers(int64_t a, int64_t b)
extern func add_integers(int $a, int $b) int {
    return $a + $b;
}

# C signature: double add_floats(double a, double b)
extern func add_floats(num $a, num $b) num {
    return $a + $b;
}

# C signature: int64_t multiply(int64_t a, int64_t b)
extern func multiply(int $a, int $b) int {
    return $a * $b;
}

Compiling as Shared Library

# Create shared library
./strada --shared extern_lib.strada

# Creates extern_lib.so with C-callable functions

Calling from C

The extern functions can be called directly from C without any Strada-specific wrappers:

/* main.c - C program that calls Strada extern functions */
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>

int main() {
    // Load the Strada shared library
    void *lib = dlopen("./extern_lib.so", RTLD_NOW);
    if (!lib) {
        fprintf(stderr, "Failed to load: %s\n", dlerror());
        return 1;
    }

    // Get function pointers
    int64_t (*add_integers)(int64_t, int64_t) = dlsym(lib, "add_integers");
    double (*add_floats)(double, double) = dlsym(lib, "add_floats");
    int64_t (*multiply)(int64_t, int64_t) = dlsym(lib, "multiply");

    // Call them directly - no StradaValue* wrappers needed!
    printf("add_integers(10, 32) = %ld\n", add_integers(10, 32));   // 42
    printf("add_floats(3.14, 2.86) = %f\n", add_floats(3.14, 2.86)); // 6.0
    printf("multiply(6, 7) = %ld\n", multiply(6, 7));                // 42

    dlclose(lib);
    return 0;
}

Compile and run the C program:

gcc -o main main.c -ldl
./main

Type Mapping

Extern functions use raw C types, not StradaValue*:

Strada TypeC Type
intint64_t
numdouble
strchar*
voidvoid
When to use extern: Use extern func when you need to call Strada code from C programs, embedded systems, or other languages with C FFI capabilities. For Strada-to-Strada library calls, regular functions with import_lib are preferred.

Extern vs Regular Functions

FeatureRegular FunctionExtern Function
Calling conventionStradaValue* parametersRaw C types
Callable from CNeeds wrappersDirect calls
Type safetyRuntime type checkingCompile-time only
Use caseStrada librariesC interoperability

Complete Example: Compression Library

Here's a complete example wrapping zlib for compression:

package Compress;

__C__ {
    #include <zlib.h>
    #include <stdlib.h>
    #include <string.h>
}

func gzip(str $data) str {
    __C__ {
        char *input = strada_to_str(data);
        size_t input_len = strlen(input);

        // Allocate output buffer (worst case: slightly larger than input)
        size_t output_len = compressBound(input_len);
        char *output = malloc(output_len);

        // Compress
        int ret = compress((Bytef*)output, &output_len,
                          (Bytef*)input, input_len);

        free(input);

        if (ret != Z_OK) {
            free(output);
            return &strada_undef;
        }

        StradaValue *result = strada_new_str_len(output, output_len);
        free(output);
        return result;
    }
}

func gunzip(str $data, int $max_size) str {
    __C__ {
        char *input = strada_to_str(data);
        size_t input_len = strlen(input);
        size_t output_len = (size_t)strada_to_int(max_size);

        char *output = malloc(output_len);

        int ret = uncompress((Bytef*)output, &output_len,
                             (Bytef*)input, input_len);

        free(input);

        if (ret != Z_OK) {
            free(output);
            return &strada_undef;
        }

        StradaValue *result = strada_new_str_len(output, output_len);
        free(output);
        return result;
    }
}

Compile with the zlib library:

./strada --shared compress.strada -- -lz

Best Practices

Always Free Strings

strada_to_str() allocates memory. Always free() the result when done.

Check for NULL

Strada values can be undef. Check before extracting to avoid crashes.

Use Static Helpers

Put complex C logic in static helper functions in top-level blocks for cleaner code.

Document Handle Types

When using the opaque handle pattern, document what type of pointer each int handle represents.

Debugging Tips

# Compile with debug info
./strada -g -c myprogram.strada

# Debug with GDB
gdb ./myprogram