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
.bdlinterface 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:
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:
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.