Build for no_std targets

Important

Support for no_std targets is still in an immature state.

  • no_std builds with POSIX support should be fully functional; however, they have not yet been thoroughly validated in production use

  • no_std builds for bare metal are currently proof-of-concept and do not yet support all features

Configuring the iceoryx2 build for no_std

As is convention in the Rust ecosystem, building for no_std is done by disabling the std feature. You can do this when specifying the dependency in your Cargo.toml:

iceoryx2 = { version = "X.Y.Z", default-features = false }

Selecting a default logger

Disabling the default features on the iceoryx2 crate also disables the features that select the default logger in the iceoryx2-bb-loggers crate. As a result, all log messages are discarded.

For POSIX targets such as QNX 8, which currently does not have std support upstream, a console logger implementation that uses the POSIX API is available and can be selected by selecting the corresponding re-exported features on iceoryx2:

iceoryx2 = { version = "X.Y.Z", default-features = false, features = ["console"]}

For bare metal targets, there are no alternatives available yet, but stay tuned!

Setting up your no_std application

When building a no_std application, you must provide implementations for certain core functionality that the standard library would normally handle.

Take a look at the no_std examples for a reference on how this could be done for both POSIX and bare metal targets.

Global allocator

Since iceoryx2 uses the alloc crate, you must specify a global allocator somewhere in the application:

#[global_allocator]
static GLOBAL: MyGlobalAllocator = MyGlobalAllocator::new();

Panic handler

All no_std applications must define a panic handler using the #[panic_handler] attribute:

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // Your custom panic logic.
    loop {
        core::hint::spin_loop();
    }
}

Application entry point

In a no_std environment, you must define an appropriate entry point for your platform. This can be done using the #![no_main] and #[no_mangle] attributes.

The #![no_main] attribute prevents the compiler from emitting the main symbol, allowing you to define your own entry point that will be called by the platform’s startup code. The #[no_mangle] attribute preserves the function name in the compiled binary so it can be located by the linker.

For POSIX targets, define a C-compatible main function that returns an exit code:

#![no_std]
#![no_main]

#[no_mangle]
extern "C" fn main() -> i32 {
    // Your application code here
    
    0 // Success
}

The startup code in the C runtime that POSIX platforms provide will automatically jump to this entry point after completing its initialization.

For bare metal targets, define a function that integrates with your platform’s startup code, for example:

#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn entrypoint() -> ! {
    // Your application code here
    
    loop {}  // Or handle exit in your platform code
}

Take a look at the startup code in the bare metal example to see how an arbitrary entry point can be integrated into the application.

Further Reading

The Rust Embedded Book

Documentation on the no_std Rust environment from the Rust Embedded Book.

https://docs.rust-embedded.org/book/intro/no-std.html
Reference Examples for POSIX targets (Rust)

Reference no_std examples for targets with POSIX support.

https://github.com/eclipse-iceoryx/iceoryx2/tree/main/examples/nostd/posix/rust
Reference Examples for bare metal targets (Rust)

Reference no_std examples for bare metal targets.

https://github.com/eclipse-iceoryx/iceoryx2/tree/main/examples/nostd/bare-metal/rust