Skip to content

BDL Rust backend

The Rust extension for the BDL (Bzd Description Language) framework. It provides the Bazel rules and code generators that turn BDL definitions into Rust code.

Key Points

  • Rust code generation - generates traits from .bdl interface definitions.
  • Bazel integration - custom rules that embed BDL into the Rust build.
  • Namespace-aware symbols - generated symbols are prefixed with the BDL namespace.

Usage

BDL definitions are consumed by depending on a bdl_library target from any rust_library:

bdl_library(
    name = "interface",
    srcs = ["interface.bdl"],
)

rust_library(
    name = "implementation",
    srcs = ["implementation.rs"],
    deps = [":interface"],
)

Library

The Rust generator converts BDL definitions into a no_std Rust file. Symbols are prefixed with the BDL namespace of the file, with each namespace part capitalized and concatenated. For example, given the following interface in interface.bdl:

namespace bzd.test;

interface MyService {
    method add(a = const Integer, b = const Integer) -> Integer;
}

The generated code provides:

#[allow(async_fn_in_trait)]
pub trait BzdTestMyServiceInterface {
    async fn add(&mut self, a: i32, b: i32) -> Result<i32, bzd::base::error::Error>;
}

The implementation implements the trait:

use rust_bdl_tests_interface_interface::BzdTestMyServiceInterface;

struct Calculator;

impl BzdTestMyServiceInterface for Calculator {
    async fn add(&mut self, a: i32, b: i32) -> Result<i32, bzd::base::error::Error> {
        Ok(a + b)
    }
}

Crate naming

The generated crate is named after the full label of the bdl_library target, with the package path and target name joined with _. This keeps crate names unique across packages. For example, //hello:interface generates the crate hello_interface, and //rust/bdl/tests/interface:interface generates rust_bdl_tests_interface_interface.

Enums

Top-level enums are generated as Rust enums with repr(u8), standard derives, and CamelCase names and variants. For example, given the following enum in interface.bdl:

namespace bzd.components.esp;

enum UartDevice {
    uart0
,   uart1
}

The generated code provides:

#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum BzdComponentsEspUartDevice {
    Uart0,
    Uart1,
}

Dependencies

When a bdl_library declares deps, the generated Rust file imports the symbols of each dependency privately:

bdl_library(
    name = "file_b",
    srcs = ["file_b.bdl"],
    deps = [":file_a"],
)

The generated code for file_b includes use file_a::*;, importing file_a's symbols for internal use without re-exporting them. Downstream users must depend on file_a directly to access its symbols.

Components

Components generate an interface trait and a context struct holding the config: values. For example:

namespace bzd.components;

component User {
config:
    username = String;
    age = Integer [min(0)];

interface:
    method print_info();
}

Generates:

pub trait BzdComponentsUserInterface {
    async fn print_info(&mut self) -> Result<(), bzd::base::error::Error>;
}

pub struct BzdComponentsUserContext {
    pub username: &'static str,
    pub age: u32,
}

When a config: entry references another component or interface, the context is generic over a ContextConstraint trait and stores the dependency as a component::ComponentHandle:

pub trait BzdComponentsManagerContextConstraint {
    type User: BzdComponentsUserInterface + 'static;
}

pub struct BzdComponentsManagerContext<C>
where
    C: BzdComponentsManagerContextConstraint,
{
    pub user: component::ComponentHandle<C::User>,
}

The component implementation provides the concrete struct and implements the trait. The runtime support crate //rust/bdl/generator/impl/adapter:component provides Component, CompositionComponent, StaticComponent, ComponentHandle, and the Lifecycle trait.

Implementation dependencies

Rust dependencies provided through the implementation attribute are re-exported as part of the public API:

bdl_library(
    name = "interface",
    srcs = ["interface.bdl"],
    implementation = {"rust": ":stream_impl"},
)

The generated code re-exports the implementation crate with pub use stream_impl::*;, publishing its symbols to downstream users.