Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Formatting Is Unreasonably Expensive for Embedded Rust (jamesmunns.com)
135 points by Twirrim on Dec 9, 2019 | hide | past | favorite | 30 comments


This is a fascinating example of how languages tend to grow up alongside assumed applications for them and, as a result, can have assumptions deeply baked in that challenge using the language in novel applications and environments.

Solving this without pushing too much complexity onto the developer (1) is going to be a fascinating challenge.

(1) What does "too much complexity" look like? The C++ standard library has template arguments for the memory allocation logic. When it's needed, it's very much needed; when it isn't, the existence of that argument was long a tax on comprehending compile-time template errors until tooling caught up with being able to intelligently hide those arguments when they're likely to be noise.


Yeah, I've been looking into wasm and am finding similar issues (not with Rust specifically). Languages designed for desktop/server usages are not a great fit in small places (embedded and the web).

With Go there's TinyGo which solves this problem (note, I haven't used it so can't speak to how well). I wonder if something similar for Rust is possible, a compiler targeting embedded systems?


The article mentions several crates that fill the role of TinyGo in Rust. It's just that they aren't the default.


There are 200+ people working on the Rust language, but only single digits are paid for it [1]. I don't take the OP as a complaint, but perhaps the embedded community is going to have to take a stab at fixing this themselves.

[1] https://xampprocky.github.io/public/blog/rust-2021/


If there's anyone from the embedded community who's taking a stab at fixing rust for that use case, it's James. He's one of the three members of the Rust Embedded Working Group (https://github.com/rust-embedded/wg).


Realistically, the embedded community will just bypass rust and stick with C, C++, and other more suitable languages.


Embedded Rust usage is growing all the time. The formatting problem is not a blocker; it's just a "papercut" per the article. C and C++ have their fair share of annoyances too.


The open minded embedded community is getting themselves busy with C++, Ada, Real Time Java, MicroPython, CircuitPython, JerryScript, Pascal, BASIC and Oberon-07.

So Rust can naturally also play there.

Then there is the embedded community stuck on Assembly and C89 mentality, where even C++ doesn't manage to get a foot on, let alone Rust.


I'm not sure how to contact the author, but just a nitpick:

    fn inner_main() -> Result<(), ()>
should be:

    fn inner_main() -> Result<!, ()>
since it shouldn't return except in the case of an error. It's important to make the compiler aware of such semantics, so that it can better optimise the program.


The author has his email (and twitter handle) in his about page (https://jamesmunns.com/about/)


He's super responsive on twitter: https://twitter.com/bitshiftmask


I think this is right, but I don't think the never type is stable.


The Never type should be stable in 1.41, I believe.


I checked, and you seem to be right. It feels weird, considering all the jokes that "never is named after its stabilization date"


The seamntics of ! is stable as far as I know. It being retconned into the Never type is the unstable bit.


super small nitpick on article formatting:

> All examples provided below are sourced from this repository.

In that sentence "this repository" is a link however it has no affordance [1] until you hover a mouse over it.

1. https://uxplanet.org/ux-design-glossary-how-to-use-affordanc...


Not a super small nitpick at all, but rather extremely valuable feedback. I read through the whole article assuming the author forgot each and every link.


Doesn't C's printf have the same problem?


On embedded systems, it is quite common to have cut down libraries that only support a subset of types. e.g. floating point formatting is quite regularly left out.


Not really.

It seems the problem for the author is that either they get string based output with a big printf, or no printf at all, and replacing printf is hard. In c replacing printf with a tiny limited implementation is easy and common.


Printf is basically a runtime interpreter, which has better code size, and worse performance, than the typed template magic that Rust uses. As other comments point out, though, printf in C can be a performance problem in embedded for this reason. Pick your poison…


Yes, in the sense that calling it anywhere will pull in a lot of stdlib stuff by default. But no, in that it's trivial in C to write your own minimal printf replacement. Because of the nature of linking in C, it will override all uses of it.


In practice no, for several reasons:

Embedded compilers quite often will include two implementations of printf/sprintf - 'minimal' and 'full' (like solution 2 in the post). Usually the 'minimal' implementation doesn't have floating point support and suchlike so is sufficient for normal debugging output.

The sort of libraries that you depend on in embedded development don't implicitly print. There's none of the 'implicit panics will cause an extra 15k of space'.

It's a normal symbol fed to the linker rather anything special and compiler managed so it can be overriden if necessary.


C doesn’t have ubiquitous calls to panic, so even if it did you generally don’t get it unless you do something which more obviously interacts with the formatting parts of the standard library.


When using FreeRTOS tasks, any use of printf() requires a substantially larger stack allocation per task. But I don't recall now any massive blow-out in binary size.


Yeah canned printf is really stack heavy if I remember. A solution is to have one thread that handles printf. Biggest issue I've run into with printf at least in C is calling it tends to be expensive.


Reading this blog post I get the feeling the author is attempting to solve a problem that doesn't really need to be solved in rustc in my opinion. unwrap() typically wouldn't be in any application you actually care about errors beyond initial debugging. In production you'd either deal with the error in a sane way or fail gracefully (panic-persist or panic-reset make a lot of sense in those scenarios). For actually doing a log like format on a arm cortex-m I'd be looking at ufmt as mentioned in the post.

That's not to say the post isn't enlightening. It is, the cost is unexpectedly high in space. That's not to say that the goals of making unwrap and panic more space efficient are good ones as well. I don't think they're a concern I personally have with rust in an embedded system though.


.unwrap() in production code is perfectly normal where the author believes other code constraints outside the scope of the type system prevent it ever having a non-truthy value. Technically the author should have used `.expect(...)` instead with a message but even if the author were to write a `match` instead, the author would have panicked in the _ case. (Think erroneous design assumptions or logical errors.)


Note that it's very rare to end up with programs with provably zero instances of panicking. Even .unwrap() is okay to have if you don't use it for errors but signaling fatal, unexpected logic bugs. Every instance of array indexing have bound checks, and thus panics. The formatting machinery is going to end up in your binary if you don't make special precautions for it not to.


It's not just unwrap, it's anything that ends up using format!. It gets used in production all the time, not to mention cases where you know an Option is a Some-variant so unwrap'ing is safe.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: