Embedded developers often encounter the following problem: when trying to implement simple character input and output, they end up struggling with <span>fmtlib</span>, <span>iostream</span>, and <span>printf</span>, causing a sudden increase in binary size, and the exception handling and heap allocation can be quite alarming; or they may not receive output on RTOS and have to dig through the source code for a long time. It would be great to have an IO library that is both small and fast, and can also perform compile-time checks. Next, we introduce emio.

Why Choose emio?
- • Ultra-small size: Designed for bare metal and RTOS, the binary size is only
<span>fmtlib</span>’s 1/38! - • Zero exceptions: No exceptions thrown, unified return of
<span>emio::result</span>, making error handling more controllable. - • Zero heap allocation: By default, no heap is used, making memory more predictable.
- • Compile-time safety: Both high-level and low-level interfaces can be used in a
<span>constexpr</span>environment, allowing some errors to be exposed early.
High-Level Interface: Easy to Use and SafeTo format a string, you only need a few lines:
std::string str = emio::format("The answer is {}.", 42);
int answer{};
auto res = emio::scan(str, "The answer is {}.", answer);
if (res) {
emio::print("The answer is {}.", answer);
}
Doesn’t it look a lot like <span>fmtlib</span>? But it has no hidden dependencies, and the entire library is only a few dozen KB after compilation. <span>emio::scan</span> can also handle both formatting and reverse scanning, conveniently parsing the input string into variables.
Low-Level Interface: Manual Control for Greater ConvenienceSometimes you just want to manually concatenate a few characters in a buffer or sequentially write a few fields; in this case, use <span>writers</span> and <span>readers</span>:
emio::static_buffer<128> buf;
emio::writer wrt{buf};
wrt.write_str("In decimal: ").value();
wrt.write_int(42).value();
wrt.write_char('.').value();
buf.view(); // <- "In decimal: 42."
Now for reverse parsing:
emio::reader rdr{"17c"};
EMIO_TRY(uint32_t number, rdr.parse_int<uint32_t>()); // <- 17
EMIO_TRY(char suffix, rdr.read_char()); // <- 'c'
You can use the lowest-level methods to “build” strings in the order you want and also parse them yourself.
Static Buffer: No Heap for Greater Peace of MindHeap management on bare metal and RTOS is often poor, with fragmentation, OOM, and unpredictable delays being nightmares.<span>emio::static_buffer<N></span> provides you with a fixed-size buffer, and all operations are performed in stack or static memory, without any hidden malloc; each write either succeeds or fails, and the maximum delay is clear.
Compile-Time Friendly: <span>constexpr</span> ChecksIf you want to format a string at compile time, no problem:
constexpr auto s = emio::format("Pi approx: {:.2f}", 3.1415);
static_assert(s == "Pi approx: 3.14");
This capability is particularly useful for static data verification and generating constant tables in firmware.
How to Use emio in Your Project?
- • CMake:
FetchContent_Declare( emio GIT_REPOSITORY https://github.com/Viatorus/emio.git GIT_TAG main GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(emio) - • Single Header File: The single header file packaged by Quom can also be copied into your project.
- • Conan Center: Directly
<span>conan install emio/0.x@</span>. Remember to enable C++20 in your project; the author has tested it on GCC11/12/13 and Clang16/17.
Beta Status, Contributions Welcomeemio is still in beta, and the design document contains many “I want to do this” or “not yet implemented” notes. If you are interested in firmware IO, uniform delay, or bare metal exception safety, feel free to open an issue or write a patch. Contribution guidelines, API documentation, and online demos are waiting for you in the repository.
When emio Meets Embedded: What Pain Points Does It Solve?
- 1. Size Anxiety: No longer need to drag in fmtlib for a small feature.
- 2. Controllable Errors: Result objects replace exceptions, making all paths clear.
- 3. No Heap Requirement: Static buffer + stack priority strategy allows low-end hardware to be used.
- 4. Compile-Time Safety: Early detection and correction reduce the hassle of on-site debugging.
- 5. Dual API: High-level for quick use, low-level for customization, meeting various scenarios.
Summaryemio is a character IO library specifically designed for bare metal, RTOS, and resource-constrained scenarios. It is compact, safe, and performs excellently, while also helping you catch errors at compile time. If you are struggling with binary bloat, hesitant to use heap allocation, or facing uncontrollable exceptions, or if you want to format, parse, and print with just one line of code in firmware, give emio a try to slim down your project and make it more reliable!
Project Address:https://github.com/Viatorus/emio