This is a tran­script of a talk I gave at the Vil­nius Rust meetup. You can down­load slides here or simply fol­low along.

Note that con­tent on this page will get out­-of-d­ate fairly quickly (within months), so check the date above.

It would not be an ex­ag­ger­a­tion to say that em­bed­ded is om­ni­present. You can find em­bed­ded firm­ware every­where from fridges, mi­crowaves and per­sonal com­puters to safety-crit­ical ap­plic­a­tions in auto­mot­ive, med­ical fields, etc.

Most of this soft­ware is still writ­ten in C or C++, and neither of these, given their non-ideal track re­cord in re­la­tion to se­cur­ity crit­ical soft­ware, are the most con­fid­ence in­spir­ing choice for safety-crit­ical sys­tems.

In my ex­per­i­ence, bugs oc­cur­ring in em­bed­ded firm­ware tend to be fairly sim­ilar to those com­monly found in the user­-space soft­ware. That is:

Un­like in the user­-space, con­veni­ences such as MMU or ad­dress san­it­izer are not used, less cap­able or not present at all. This means that more of the bugs go un­der the radar and, when dis­covered, more dif­fi­cult to de­bug.

Rust man­aged to re­duce pres­ence of these bugs in the user­-space soft­ware and is well po­si­tioned to work its ma­gic on em­bed­ded firm­ware as well.

Be­fore re­writ­ing all your em­bed­ded pro­jects in Rust, it is prudent to con­sider Rust’s sup­port for your hard­ware. Among the ar­chi­tec­tures nat­ively sup­por­ted by the Rust com­piler ARM and MSP430 are the ones in­ter­est­ing for em­bed­ded use cases.

If your pro­ject uses an ARM-­based chip, you’re in luck – in terms of qual­ity, the sup­port for this ar­chi­tec­ture is com­par­able to, say, x86. Sup­port for MSP430 chips is built into the com­piler as well, how­ever its backend less battle­-­tested and the lib­rary com­pon­ents are more prone to is­sues due to the eso­teric nature of a 16-bit ar­chi­tec­ture.

Of­ten, due to their cheaper price or, per­haps, design con­straints, ar­chi­tec­tures such as AVR are em­ployed. rustc does not ship nat­ive sup­port for any of these ar­chi­tec­tures, but there might ex­ist a fork that im­ple­ments sup­port for these ar­chi­tec­tures. Forked rustc not hav­ing “of­fi­cial” sup­port and po­ten­tially di­ver­ging out­-of-d­ate from the ori­ginal pro­ject are the usual caveats, though.

Then, there are the ar­chi­tec­tures for which no LLVM backend ex­ists. Those, by ex­ten­sion, are un­likely to be sup­por­ted by rust any­time soon. For these, you’re stuck with (often, man­u­fac­turer provided) C tool­chain.

mrustc makes it pos­sible to use Rust with any ar­chi­tec­ture for which a C com­piler ex­ists, but is still an ex­tremely ex­per­i­mental tech­no­logy.

To mean­ing­fully pro­gram a mi­cro­con­trol­ler, it is im­port­ant to have ac­cess to the peri­pher­als and func­tions it ad­vert­ises. Man­u­fac­tur­ers provide mi­cro­con­trol­ler­-spe­cific lib­rar­ies us­able within C/C++. These lib­rar­ies ex­pose the re­gisters and con­veni­ence func­tions to con­trol the peri­pher­als and some­times even ex­ample driver im­ple­ment­a­tions.

These aren’t dir­ectly us­able within Rust, but not all is lost! Man­u­fac­tur­ers also tend to provide de­scrip­tion of the re­gisters in some ma­chine-read­able format. One such form­at, SVD, is pretty pop­u­lar and there ex­ists the svd2rust tool to gen­er­ate nice Rust wrap­pers to ac­cess these re­gisters.

Re­gisters, how­ever are not the end of the story… As I men­tioned earli­er, man­u­fac­turer lib­rar­ies also provide con­veni­ence lib­rary func­tions as well as ex­ample driver im­ple­ment­a­tions. This is where embedded-hal comes in. embedded-hal provides a num­ber of traits which ex­pose com­mon con­cepts in em­bed­ded pro­gram­ming in a port­able and safe man­ner.

Safety as­pect is fairly self-­ex­plan­at­ory and the port­ab­il­ity as­pect is where embedded-hal shines the most, I think. Con­sider for ex­ample a driver writ­ten against the non-­port­able man­u­fac­turer­-­provided lib­rar­ies. If you wanted to use the same driver with some other MCU, there would be a non-trivial amount of port­ing ef­fort ne­ces­sary. With embedded-hal, all the device-spe­cific parts are prop­erly ab­strac­ted, which makes these drivers port­able and use­ful when put on crates.io.

There, of course, is a trade-off of hav­ing to im­ple­ment these traits for each mi­cro­con­trol­ler fam­ily, pos­sibly by your­self, if no crate im­ple­ment­ing them ex­ists on crates.io yet.

Cur­rently crates.io has ap­prox­im­ately 40 mi­cro­con­trol­ler sup­port crates. Ma­jor­ity of these are just gen­er­ated re­gister bind­ings, how­ever there are also quite a few crates provid­ing higher level ab­strac­tions and im­ple­ment­a­tions of the embedded-hal traits.

As a part of firm­ware de­vel­op­ment, it is very likely you’ll need a real-­time op­er­at­ing sys­tem as well as lib­rar­ies for com­monly en­countered tasks such as com­mu­nic­at­ing over TCP/IP. Rust’s quickly evolving eco­sys­tem already has lib­rar­ies for many of these tasks. These lib­rar­ies are well doc­u­mented and im­ple­men­ted, but are less wide­spread and may be harder to get help with.

Even then, if the pure Rust lib­rar­ies do not serve your needs well enough, Rust’s great FFI sup­port provides all means ne­ces­sary to in­clude the pop­u­lar C lib­rar­ies into your pro­ject.

All that be­ing said, em­bed­ded firm­ware can­not yet be mean­ing­fully de­veloped in stable Rust. Many tasks still re­quire un­stable fea­tures to be achieved. Some of them, such as abil­ity to define the panic_fmt lan­guage item, are ex­pec­ted to be­come stable very soon, while some other fea­tures, such as in­line as­sembly still need a fair amount of design work be­fore their sta­bil­isa­tion will be con­sidered.

Stable fea­tures, however, aren’t the whole story. Em­bed­ded de­vel­op­ment also re­lies on a num­ber of “fea­tures” that aren’t ex­pli­citly tracked for their sta­bil­ity in Rust. Minor changes to, say, link­ing or code gen­er­a­tion, while com­pat­ible in the user­-space world, may of­ten cause is­sues for em­bed­ded firm­ware.

Con­sider an in­cid­ent that happened just this week: a re­cent change to the com­piler up­graded LLVM to a new ver­sion. This up­grade changed the op­tim­isa­tion pipeline enough, that a cer­tain firm­ware be­came just 500 bytes lar­ger than it was be­fore. With this in­crease, the com­piled code would no longer fit into the flash stor­age of the mi­cro­con­trol­ler and would fail to link! Some­thing that would­n’t usu­ally be con­sidered a break­ing change ended up break­ing ac­tual code!

So, in sum­mary, can you use Rust to de­velop your em­bed­ded firm­ware? Ab­so­lutely. Ab­so­lutely, as long as you are will­ing to deal with slightly less ma­ture eco­sys­tem, pay the up­front cost for fu­ture be­ne­fits and fix the oc­ca­sional break­age caused by com­piler changes. In re­turn Rust will give you its su­per­ior static ana­lyses and a more-­cor­rect end res­ult.

Q/A

I was asked a few ques­tions dur­ing the Q/A ses­sion after the talk. Sadly, I do not re­mem­ber their ex­act phras­ing any­more, so they are all para­phrased.

Ob­tain­ing libcore for em­bed­ded tar­gets

rustc does not ship with pre­com­piled libcore for em­bed­ded tar­gets, how­ever with the xargo tool us­ing libcore and other lib­rar­ies is as easy as spe­cify­ing a de­pend­ency on a crates.io crate.

Us­ing stand­ard col­lec­tions such as HashMap in em­bed­ded de­vel­op­ment

The stand­ard lib­rar­ies are split into more than just libcore and libstd. Quite a few col­lec­tions live in liballoc (NB: dur­ing ac­tual Q/A I called this libcollections; what a blun­der). HashMap in par­tic­u­lar de­pends on ran­dom num­ber gen­er­a­tion to work, so it is cur­rently liv­ing in libstd and is not dir­ectly us­able within em­bed­ded con­text.

libstd in em­bed­ded and wasm

Em­bed­ded and wasm are in­deed very sim­ilar in their con­straints in that they both are very low-­level en­vir­on­ments. Fairly sim­ilar prob­lems seem to be ap­plic­able to both (e.g. code size aware­ness, no libstd).

You can use libstd in wasm, but we ended up im­ple­ment­ing most of the func­tion­al­ity (such as filesys­tems) with unimplemented!(), so you have to be care­ful about what you end up us­ing.

One of the ideas run­ning around is to “unify” the libcore, libstd and friends and in­stead to rely on fea­tures and cfg to ex­pose what is us­able for a par­tic­u­lar tar­get. This would also en­able us­ing libstd within em­bed­ded.

What mi­cro­con­trol­ler to use?

I re­com­mend the Blue Pill (ST­M32F103C8). It runs at 72M­Hz, has 128kB of flash. One can be got­ten from the Chinese (Ali­ex­press) for a small price of €1.50.

Plus, the eco­sys­tem sup­port for it is great.