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:

Memory bugs: double frees, leaks, in­valid frees, use after free, out of bound ac­cesses, etc;

Data races: em­bed­ded tends to not have mul­ti-­core, but has peri­pher­als that may ac­cess memory con­cur­rently. A com­mon source of data race bugs. An­other com­mon mis­take is non-atomic modi­fic­a­tion of global data; and

Lo­gic bugs in gen­er­al.

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.