4.1 KiB
rt_macros Usage Guide
rt_macros is a collection of Rust procedural macros for RT-Thread. It helps export Rust functions as system entry points, initialization entries, or shell commands in a no_std environment, so they integrate cleanly with RT-Thread’s startup and command mechanisms.
Background and Goals
In no_std mode, the traditional main function is not directly available. rt_macros generates C-ABI-compatible wrapper entries and the required link-section items so RT-Thread can discover and invoke the corresponding Rust logic during startup or shell command scanning.
Provided Macros (aligned with RT-Thread conventions)
-
rt_thread_main!: Marks the Rust main-thread entry.- Function signature:
fn()(no parameters, no return value). - Constraints: no
const,unsafe,async, varargs; no explicit ABI; no generics; default visibility (non-pub). - Purpose: exports an entry that RT-Thread can recognize and call as the main thread.
- Function signature:
-
rt_component_export!: Exports a component initialization entry.- Function signature:
fn(). - Same constraints; optional parameter:
name = "..."(used to generate internal symbol names). - Link section: places the function pointer into
.rti_fn.4, executed during the component initialization phase.
- Function signature:
-
rt_app_export!: Exports an application initialization entry.- Function signature:
fn(). - Same constraints; optional parameter:
name = "...". - Link section: places the function pointer into
.rti_fn.6, executed during the application initialization phase.
- Function signature:
-
msh_cmd_export!: Exports an RT-Thread shell command.- Function signature:
fn(args: vec::IntoIter<rt_rust::param::ParamItem>). - Attributes:
name(required, command name),desc(optional, command description). - Link sections and data:
- Generates a command descriptor struct placed into
FSymTab. - Command name and description are
\0-terminated byte arrays placed into.rodata.name.
- Generates a command descriptor struct placed into
- Invocation wrapper: generates an
extern "C"wrapper(argc, argv), convertsargvtoVec<ParamItem>, and calls the original Rust command function.
- Function signature:
Link Sections and Startup Flow
- Component init entries:
.rti_fn.4 - Application init entries:
.rti_fn.6 - Shell command table:
FSymTab - Shell name/description strings:
.rodata.name
During startup or shell scanning, RT-Thread traverses these sections to register or invoke the corresponding Rust logic.
Usage Examples
Main-thread entry
use rt_macros::rt_thread_main;
#[rt_thread_main]
fn main() {
// Main thread logic here
}
Component export
use rt_macros::rt_component_export;
#[rt_component_export(name = "rust_component_registry")]
fn my_component_init() {
// Component init logic
}
Application export
use rt_macros::rt_app_export;
#[rt_app_export(name = "rust_app_example")]
fn my_app_init() {
// Application init logic
}
Shell command export
use rt_macros::msh_cmd_export;
#[msh_cmd_export(name = "hello", desc = "Say hello")]
fn hello_cmd(args: vec::IntoIter<rt_rust::param::ParamItem>) {
// Handle args and print output, etc.
}
Interacting with C Code
When calling exported Rust entries from C, declare the prototype and use extern "C" for the calling convention, for example:
extern "C" void rust_function_name(void);
The command export macro generates a wrapper with (argc, argv); RT-Thread’s command system calls this wrapper and passes the arguments to the original Rust function.
Common Issues and Diagnostics
- Compile-time errors: the macros produce explicit diagnostics if the function signature does not match (e.g., must be
fn()or a specific parameter shape). Adjust the function to meet the requirements above. - Visibility requirement: keep the entry function at default (non-
pub) visibility to satisfy macro constraints. allocdependency: the shell command macro usesalloc::vec::Vec, ensure an allocator is available at runtime (RT-Thread usually provides one).