Closed chrysn closed 2 years ago
The last time I looked into Rust they were still porting the stdlib to Rust. It wasn't possible to use the std on embedded devices. The issue is, that most of the Rust libraries are using std. So you have the option to rewrite the lib, so it doesn't use std anymore or to port the std parts you need. Besides that, there were a ton of other issues.
Found a good overview (January 2018) about the current situation: http://blog.japaric.io/embedded-rust-in-2018/
On Mon, Aug 20, 2018 at 10:45:47PM -0700, Philipp wrote:
The last time I looked into Rust they were still porting the stdlib to Rust. It wasn't possible to use the stdlib on embedded devices.
I'm aiming for no_std programs here (maybe could have made that clearer in the original post). While time and again there are some things in std that should IMO go into core, no_std is roughly equivalent to the style of C development I've done previously in the embedded area. (Ie. no file system, no dynamic memory management, no threads or sockets. If any of that does get implemented, it's not general-purpose and used with a bespoke API, eg. a dedicated network buffer with its send and receive calls rather than generic heap allocation and POSIX-style sockets).
Besides that, there were a ton of other issues.
Yes there are, but there is an active effort within the Rust community to enhance things; especially the stable situation is improving and documented in the newsletters.
I even hope to uncover more of Rust's shortcomings in the embedded area in the course of this issue, but I'm confident that they can be fixed.
@chrysn True. I just want to make the current situation clear. :) I would be really happy to use RIOT with Rust :) There are already some really interesting driver implementation & libraries: https://github.com/rust-embedded/awesome-embedded-rust
[…] @nmeum, what were your applications […]?
We were mostly interested in the safety features offered by Rust (e.g. memory safety) and thus were interested in using it in RIOT. However, due to the reasons outlined below, we ditched this after a while and experimented a bit with using Checked C in RIOT: https://github.com/beduino-project/riot-police
[…] and what are your expectations on build system integration?
The built system integration will likely be not as easy as a you might think at least not if you try to integrate Rust into the make based buildsystem as we did in #6162. Using xargo that might be easier. See also https://github.com/rust-lang/rust-roadmap-2017/issues/12
[…] do you think one of the build system integration approaches should be resurfaced?
I think trying to integrate Rust into the make-based build system again is probably a bad idea. Due to the fact that the build system builds modules listed in USEMODULES
in sequential order (which doesn't play well with rust if modules depend on each other since all dependencies need to be built first IIRC) we weren't, among other things, able to enable parallel builds.
How far did you get with making RIOT's APIs usable from Rust?
As we only wrote Rust FFI wrappers for RIOT modules written in C, we got quite far and even managed to built Rust applications on top of gnrc_udp with our Rust integration. https://github.com/beduino-project/RIOT contains are more up-to-date integration than the one in #6162.
However, upstream didn't really seem to like the fact that you have to maintain two separate code bases (Rust and C) then, so a better approach would probably be rewriting C code in Rust and providing a C interface (which also has some benefits from a security perspective).
@nmeum wrote:
Checked C
IMO any integration can only succeed if it does not require large changes to RIOT; there is, AFAICT, too large a user and developer base that are does not have the need to migrate away from anything to whom maintaining Checked C annotations in the code is much more of a hassle than it brings.
Same goes for maintaining separate code bases: if Rust (or any static checking support that exceeds sane coding practice like "label const
what can be") needs additional API wrappers (which it will, otherwise applications need to have unsafe code all the time), those should be maintainable separately. RIOT users are of course welcome to contribute, but nobody can be stopped from adding to RIOT because they can't or don't want to maintain Rust wrappers.
For that reason, the approach I'm currently following on the integration side is to have a riot-sys
crate that creates low-level unsafe Rust bindings to the C code (think extern "C" fn uart_stdio_read(buffer: *mut [u8], len: isize) -> isize
), ideally automatically per-project. A set of safe-to-use wrappers can then be maintained as riot-thread
, riot-shell
, riot-gnrc
etc.
at least not if you try to integrate Rust into the make based buildsystem
I was afraid so. At least for my applications, not having any formal integration is good enough -- there's a configured RIOT base system and a Rust application that's linked in, and they don't need to know much of each other. What did come to light trying to wrap GNRC is that it's paramount for the creation of struct wrappers to extract the build flags / headers from the RIOT build process, for any mismatch in ifdefs causes struct-ural differences (one language expects a field to be there and the other not).
Chances are (I'm just experimenting with this) that the minimal "interface" between the build systems would be that RIOT exports a riot-all.h
file created using gcc -E
(where riot-all.h includes all the system, device etc. headers, and the compiler expands all preprocessor statements, thus baking in the -D
flags from the RIOT build system). Then cargo runs bindgen on that, thus creating a riot-sys
crate. It builds a static library from that, which is then linked in by the RIOT build system in the last step.
What was your intention behind making the Rust application into individual RIOT modules? Did you expect multiple Rust code parts to be present at the same time?
even managed to built Rust applications on top of gnrc_udp
How did you manage the lack of lifetimes? For example, when wrapping SAUL, I'm getting no guarantees of how long the handle will be valid, as saul_reg_rm
might be called at any time (maybe even in an interrupt), and any measurement on the device can be undefined behavior after that.
@Citrullin wrote:
some really interesting driver implementation & libraries [...] awesome-embedded-rust
Yes. One of the test cases I'm running is to wrap RIOT's i2c as an embedded-hal blocking I2C device and letting an established driver[1] work its magic on it.
[1]: https://github.com/klemens/si7021-rs -- not using embedded-hal yet, but I'm preparing a PR to that effect
The WIP crates are now published as riot-wrappers and and riot-sys, along with small examples; they were initially presented during toda^Wyesterday's Rust meetup.
Compared to the earlier sketches, the issues of having varying type definitions have been overcome -- or rather, accepted: None of the crates involved builds without a RIOT_CFLAGS
environment configured to RIOT's $(CFLAGS_WITH_MACROS) $(INCLUDES)
; those flags are needed anyway for bindgen to build the structs right (like whether -fshort-enums
is present), and lifts the need to have a preprocessed header file.
This has also brought the build system integration to a point where I'd consider it usable; the cargo integration boilerplate is now down to the size of an example Makefile, as in this example. (It'd be a bit more if DEVHELP=0 were to trigger --release
builds).
Please have a look at work so far and let me know if you have suggestions for its further development.
I was recently made aware by some rust support implemented by a student of @kaibeckmann: maybe you can have a look there as well?
On Wed, Jan 30, 2019 at 10:17:15AM +0000, Martine Lenders wrote:
I was recently made aware by some rust support implemented by a student of @kaibeckmann: maybe you can have a look there as well?
I had not found those. Thanks -- sure, having a look, but from a first glance there's much overlap. Getting in contact with Ben to avoid further duplication.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you want me to ignore this issue, please mark it with the "State: don't stale" label. Thank you for your contributions.
I'm interested in contributing to RIOT, but am more a Rust dev than C (know both). Would devs be willing to reopen this issue if there is new interest in incorporating Rust into RIOT?
It was suggested to maybe write a PoC peripheral in Rust with glue code (w/ embedded_hal
) to use in the rest of RIOT. Happy to start there. Thoughts?
Those do exist, as the riot-sys (equivalent to a PAC) crate, the riot-wrappers (implementing ADC, Delay and other embedded-hal traits) and the riot-examples at https://gitlab.com/etonomy/riot-examples/.
I'm in the process of preparing examples that can be placed in the RIOT examples folder, with one demoing an application written in Rust, and one showing off a module.
Those do exist, as the riot-sys (equivalent to a PAC) crate, the riot-wrappers (implementing ADC, Delay and other embedded-hal traits) and the riot-examples at https://gitlab.com/etonomy/riot-examples/.
Wasn't aware of those, that's awesome!
I'm in the process of preparing examples that can be placed in the RIOT examples folder, with one demoing an application written in Rust, and one showing off a module.
Let me know if there are any review or contributions wanted, this sounds like exactly what I was looking for. Much appreciation.
However, since this is your tracking issue @chrysn, I think this issue can stay open ;-).
Type: tracking
will keep the stale bot away ;-).
I'm in the process of preparing examples that can be placed in the RIOT examples folder, with one demoing an application written in Rust, and one showing off a module.
Loking forward. I just started seriously getting into Rust, I can rewiew it.
@chrysn building the saul_blink
example from riot-examples
succeeds for BOARD=stm32l476g-disco
. But it fails for others. For example BOARD=sodaq-sara-sff
, gives
cargo:rerun-if-changed=riot-bindgen.h
--- stderr
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:13:9: warning: 'RIOT_APPLICATION' macro redefined [-Wmacro-redefined]
note: previous definition is here
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:14:9: warning: 'BOARD_SODAQ_SARA_SFF' macro redefined [-Wmacro-redefined]
note: previous definition is here
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:16:9: warning: 'CPU_SAMD21' macro redefined [-Wmacro-redefined]
note: previous definition is here
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:18:9: warning: 'MCU_SAMD21' macro redefined [-Wmacro-redefined]
note: previous definition is here
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:20:9: warning: 'RIOT_VERSION' macro redefined [-Wmacro-redefined]
note: previous definition is here
riot-bindgen.h:17:9: warning: 'UINT16_MAX' macro redefined [-Wmacro-redefined]
/usr/lib/llvm-10/lib/clang/10.0.0/include/stdint.h:601:9: note: previous definition is here
riot-bindgen.h:18:9: warning: 'UINT32_MAX' macro redefined [-Wmacro-redefined]
/usr/lib/llvm-10/lib/clang/10.0.0/include/stdint.h:557:10: note: previous definition is here
/home/kees/src/RIOT/cpu/cortexm_common/include/cpu.h:33:10: fatal error: 'stdio.h' file not found
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:13:9: warning: 'RIOT_APPLICATION' macro redefined [-Wmacro-redefined], err: false
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:14:9: warning: 'BOARD_SODAQ_SARA_SFF' macro redefined [-Wmacro-redefined], err: false
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:16:9: warning: 'CPU_SAMD21' macro redefined [-Wmacro-redefined], err: false
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:18:9: warning: 'MCU_SAMD21' macro redefined [-Wmacro-redefined], err: false
/home/kees/src/riot-examples/saul_blink/bin/sodaq-sara-sff/riotbuild/riotbuild.h:20:9: warning: 'RIOT_VERSION' macro redefined [-Wmacro-redefined], err: false
riot-bindgen.h:17:9: warning: 'UINT16_MAX' macro redefined [-Wmacro-redefined], err: false
riot-bindgen.h:18:9: warning: 'UINT32_MAX' macro redefined [-Wmacro-redefined], err: false
/home/kees/src/RIOT/cpu/cortexm_common/include/cpu.h:33:10: fatal error: 'stdio.h' file not found, err: true
thread 'main' panicked at 'Unable to generate bindings: ()', /home/kees/.cargo/registry/src/github.com-1ecc6299db9ec823/riot-sys-0.3.2/build.rs:60:10
I s'ppose there's an update due here:
Aren't there still some tasks here left to do?
You're right, the "closes:" commit resolved most but not all of this.
What's open -- apart from increasing the oxidlzed API surface, but that's incremental and out of scope for here -- is support for libraries or other system components. There's a follow-up in #16833. What's in there lets Rust modules put code into XFA, and given how we often extern void foo_init(void);
, that should do -- we might consider going evem further (defining user API in Rust with externs and cbindgen'ing headers), but that's probably more for 3rd party libs that do that anyway.
The rest is gradual improvements.
Reopening until non-app modules work (spoiler: works already in #16833, just needs dedusting & rebase), but apps are doable now already.
Support for RIOT modules has been included already in 2022.07.
I think we can close this now -- going through the last open items:
extern void foo(void);
(all current examples go through the OS, but nothing is keeping them)
It would be nice to be able to write applications in Rust that would run on a RIOT board, especially when sharing libraries with non-embedded systems. Before I plunge head-first into a third attempt (after #5740 and #6162), I'd like to look at an outline of what needs to be done for this to be usable, and before that, to look at what being usable actually means in this case.
Usage scenarios I see are (to be ticked when there's actual need for them):
Technically, the nontrivial tasks I see are:
#define UART_STDIO_DEV irrelevant
and assert that it has no impact on the actual API functions)uart_stdio_write
usable by Rust'swrite!
(not actually tricky but done)Given my use cases, my exploratory work is more focused on the API part; I'd like to have a "raw" (possibly bindgen generated) API that more or less just gets rid of the
extern "C"
parts, and Rust's famous safe zero-cost abstractions on top of that. Ideally, RIOT peripherals could thus implement the embedded-hal interfaces, making the OS agnostic drivers usable immediately.As far as the build system is concerned, my use case comes with low expectations towards integration: what I currently do works reasonably well, which is making cargo output a static library for the application, and having a rump RIOT application that calls once into a rust_main(), which then sets up any threads etc. (Quite possibly, having main in Rust could work just as well). The only thing I needed in the build system was to set
BASELIBS+=.../target/.../libapplication.a
; from there, everything builds and flashes fine.@kaspar030, @nmeum, what were your applications, and what are your expectations on build system integration? Given that embedded Rust has come some way in the meantime (eg. xargo is not needed any more), do you think one of the build system integration approaches should be resurfaced? How far did you get with making RIOT's APIs usable from Rust?
[Apologies for the many editing iterations; my new keyboard is more Ctrl-Return happy than my old one. This is the final version of the issue's initial comment, except for later additions to the checkboxes which are accompanied by new comments on the thread.]