Stability

Learn how lla's plugin system maintains ABI stability through Protocol Buffers and FFI layers. Understand how plugins can remain compatible across different lla versions and Rust compiler versions through careful API design and message-based communication patterns.

ABI Stability

lla’s plugin system keeps ABI stable across lla and Rust versions. Plugins remain compatible.

Core Architecture

Message-Based Foundation

Protocol Buffers is the communication layer between lla and plugins:

message PluginMessage {
    oneof message {
        bool get_name = 1;
        bool get_version = 2;
        bool get_description = 3;
        // ... other fields
    }
}

Benefits:

  • No Rust ABI coupling
  • Language-neutral
  • Version-resilient

Cross-Language Communication

The FFI layer uses C-compatible types:

#[repr(C)]
pub struct RawBuffer {
    pub ptr: *mut u8,
    pub len: usize,
    pub capacity: usize,
}
 
#[repr(C)]
pub struct PluginApi {
    pub version: u32,
    pub handle_request: extern "C" fn(*mut std::ffi::c_void, *const u8, usize) -> RawBuffer,
    pub free_response: extern "C" fn(*mut RawBuffer),
}

Versioning

Every plugin declares its API version:

pub const CURRENT_PLUGIN_API_VERSION: u32 = 1;

The loader checks compatibility.

Data Interchange

Rich Metadata Exchange

File metadata is exchanged via Protobuf:

message EntryMetadata {
    uint64 size = 1;
    uint64 modified = 2;
    uint64 accessed = 3;
    uint64 created = 4;
    bool is_dir = 5;
    bool is_file = 6;
    bool is_symlink = 7;
    uint32 permissions = 8;
    uint32 uid = 9;
    uint32 gid = 10;
}

Extensible Design

Plugins can add custom fields:

message DecoratedEntry {
    string path = 1;
    EntryMetadata metadata = 2;
    map<string, string> custom_fields = 3;
}

Implementation Intelligence

Loading

libloading handles dynamic loading with isolation and message passing.

Type Safety

  • Strict Protobuf schemas
  • FFI-compatible types
  • Clear conversions

Creating Plugins

Start with the macros and implement the handler:

  1. Start with our FFI macros:

    declare_plugin!(MyPlugin);
  2. Implement core functionality:

    impl Plugin for MyPlugin {
        fn handle_raw_request(&mut self, request: &[u8]) -> Vec<u8> {
            // Transform requests into responses
        }
    }
  3. Handle data transformations:

    impl From<EntryMetadata> for proto::EntryMetadata {
        fn from(meta: EntryMetadata) -> Self {
            // Convert your data structures
        }
    }

Use the plugin utils to reduce boilerplate. See other plugins for examples.