Rust Language on Teensy

Status
Not open for further replies.

tiago

Member
I was just introduced to the Rust Language, https://www.rust-lang.org/en-US/, by a fellow programmer. After looking at the main site, I got pretty interested, as it seems to facilitate many programming language features that have never been available most "low-level" languages. (How often I curse the lack of lambdas and higher order functions in C++).

My first thought was this would be great if it ran on a teensy 3.x, as that is my favorite embedded system by far :). Apparently, it does: http://jamesmunns.com/update/2016/09/26/teensy3-rs.html . I posted this here, because I didn't find reference to much of the current work being done on the forum and wanted to encourage anyone that can help him to do so.

I would really like to have a few of you other dedicated Teensy users take a look and see what you think. I'm especially interested to hear what Paul thinks, although I understand he is pretty busy with a million other things.

Here is my 2 cents:

I love the idea of it. I'd be willing to bet that this becomes pretty substantial in the future. (C++ obviously has big issues if Google created Go and a community is springing up around Rust so quickly.)

Like all tools, I really dislike discussion that devolves into which is better, Rust vs C++, but much prefer to focus on what types of problems are best solved by each.

I think that at some distant future time, this could be a much better experience for beginners. (Although that is a long time off, and it would probably have to make it into Arduino IDE for that to happen)

The switch to a "functional" language can be tough on people. Some languages are near-neighbors with easy translations, while others use very different mental models. My experience to this was when I started using Scala 5 yrs ago. I made stuff work, but it took a long time to really enjoy the benefits and to do the awesome stuff. New guys that don't know any different will actually probably find it easier to write good Rust than those that are already have lots of experience. My own learning experience was very worthwhile. It thought me many new ways to model and decompose problems. I was introduced to the concept of "cognitive load". The job of the language and IDE are to reduce the cognitive load necessary to solve a specific problem. When this happens, you do more faster. (An example of this: Scala makes it easy to use immutable data, and encourages it. The IDEs color code the two types differently, so when you read code you can easily reason about what can change or not.)
 
Last edited:
I'm especially interested to hear what Paul thinks

I've never used Rust and I know almost nothing about it. The one conversation I've had with someone who used it in a real project (not with Teensy) involved quite a lot of talk about how major changes occurred during the course of his work... within the last year.

I do make a mental note of requests. So far, MicroPython is by far the most requested alternate language.
 
Paul,

Thanks for taking time to respond. I guess time will tell. Things like this always take a while to settle out to see where the momentum shifts.
 
(How often I curse the lack of lambdas and higher order functions in C++).
What are you talking about? The standard Teensy toolchain is quite old at this point, but it does include the C++ standard library and supports C++11. Things like lambdas, templates and std::function work perfectly well.

The ARM Embedded Toolchain works fine with Teensy (the Teensyduino toolchain is based on an old version) and supports C++14 and generic lambdas. You can do neat stuff like this (boards.txt must be changed to enable C++14 mode):

Code:
#include <algorithm>
#include <array>

struct S {
    int v;  
};

auto sort = [](auto& container, auto extract_field_fn, bool ascending) {
    std::sort(container.begin(), container.end(), [&extract_field_fn,ascending](const auto& a, const auto& b) {
        return ascending ? extract_field_fn(a) < extract_field_fn(b) : extract_field_fn(a) > extract_field_fn(b);
    });
};

auto sort_and_print = [](auto& container, bool ascending) {
    sort(container, [](const S& s){ return s.v; }, ascending);
    Serial.println(ascending ? "ascending" : "descending");
    for(const auto& s : container) Serial.println(s.v);
};

void test() {
    std::array<S, 5> my_array = { 3, 1, 2, 0, 2 };
    sort_and_print(my_array, true);
    sort_and_print(my_array, false);
}

void setup() {
    Serial.begin(9600);
    delay(2000);
    test();
}

void loop() {
}
 
My lambda comment stands corrected. I haven't used C++ enough in the past few years to keep up with the latest changes. Thanks for the nice example.

I used lambdas as an example of a modern programming construct, in that comparison we can chalk up the two as near parity, although I think the rust syntax is cleaner. If all you need is lambdas it seems that staying with C++ is the current best choice.

However, Rust offers at least a handful of other constructs that are interesting.

Here is a quote from an article describing the experience of a Dropbox dev using Rust for their highly performant internal server code:
In the words of Jamie Turner, a lead engineer for the project, “the advantages of Rust are many: really powerful abstractions, no null, no segfaults, no leaks, yet C-like performance and control over memory.”
Still wondering if anyone on this list has taken the time to test out some of the bells and whistles.
 
My lambda comment stands corrected. I haven't used C++ enough in the past few years to keep up with the latest changes. Thanks for the nice example.

I used lambdas as an example of a modern programming construct, in that comparison we can chalk up the two as near parity, although I think the rust syntax is cleaner. If all you need is lambdas it seems that staying with C++ is the current best choice.

However, Rust offers at least a handful of other constructs that are interesting.

Here is a quote from an article describing the experience of a Dropbox dev using Rust for their highly performant internal server code:

Still wondering if anyone on this list has taken the time to test out some of the bells and whistles.

I'm very much interesting in Rust, just haven't had the time to dive into it yet. It's still evolving but seems to have a great and helpful community around it. Have you seen this project? https://github.com/mdaffin/teensy-3-rust

BTW I came across this post looking to see if I could use C++11 features like auto, lambdas, std::function et al on the Teensy, as I use them all the time now!
 
Last edited:
From what I can piece together, first-class Rust support on Teensy would require a "device crate" for the processors, which appears to be able to be automatically generated, though this doesn't yet appear to have been done.

It then requires a "board support crate" to knit together implementations of the embedded-hal interfaces for the devices included on the various Teensy boards.

If anyone can clarify my understanding or notices any of the above things have been done, please update this thread :)
 
Compiling Rust code is possible, but not within the Arduino IDE [1]. You need to create a Makefile.

For example, if you want to include a function from rust, you would code it as follows:

Code:
#![no_std]

#[no_mangle]
pub extern "C" fn run_main() -> isize {
    42 as isize
}

use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

Then you call it from C as follows:

Code:
int run_main();
void setup() {
	Serial.print(run_main());
}

Install Rust as usual. Then install ARM support with

Code:
rustup target install thumbv7em-none-eabi

Compile the file created with these options:

Code:
rustc --target=thumbv7em-none-eabi --crate-type=staticlib --emit obj test.rs -o test.o

Then just link `test.o` along with all the other `.o` files as normal.

Note [1]: The reason the Arduino IDE can't compile Rust is because it only supports C/C++/S files. The file extensions are hard-coded in the IDE.
 
I just wrapped some code (mostly scaffolding, but it works) I used for a project on Teensy LC as reusable crate:

https://gitlab.com/teensy-rs/teensy-lc/
https://crates.io/crates/teensy-lc
https://crates.io/crates/mkl26z4

A "blinky" example is included in the teensy-lc git repository, and a more complex project is at https://gitlab.com/mpasternacki/blinkenspaghettimonster/ (but the complex project is for my own use, so don't expect it to be tidy or very legible).

I also reimplemented teensy_loader_cli in Rust to accept an ELF file as input. It should work with Teensy 4, but it's libusb (Linux/FreeBSD) only. And doesn't accept ihex input. Patches for more platforms or input formats welcome, as long as they don't break ELF input on FreeBSD:) https://gitlab.com/teensy-rs/teensy-loader-rs

Next thing I plan is a set of crates similar to the teensy-lc repo, but for Teensy 4. I also have a 3.6 here, but it's not a priority for me, so maybe sometime.

WDYT?
 
I was dabbling with Rust on the Teensy 3.2 a couple of years ago - before the current set of embedded crates were sort of standardized. I've poked a bit at the Teensy 4 as well. I just started working on a higher-level / "simpler" API on top my 3.2 and 4.0 work, to provide the standard Arduino names for devices (e.g. "serial1"). I haven't pushed this anywhere yet, but hope to "soon". This is all sort of still avoiding the standard rust-embedded stuff, because I want full control of my runtime environment so I can experiment with async/futures.

AFAIK, ratkins is pretty much right about what would be needed to support the Teensy's in the standard Rust Embedded ecosystem. It looks like that's the direction mpasternacki has been going? I glanced only briefly at the repos.

I am super excited about that new loader CLI, though. I'll be trying it out tonight.
 
Yes, I'm taking the direction described by @ratkins. I'm still quite new to Rust and I'm trying to do things by the book (The Embedded Rust Book, to be precise). I took some shortcuts, though: I have one combined HAL/board crate for Teensy LC instead of HAL crate for CPU and then board crate for Teensy – but if need arises, it might be possible to split it, there's simply not enough code at the moment.

I don't find this approach limiting at all: I have raw register access from the peripheral crate, I can (but don't have to) use HAL abstractions when needed. In the peripheral crate I have safe methods for whatever's described in the SVD as legal, and I can fall back to unsafely writing anything. I haven't gotten (yet) into async/futures, but nb crate on which HAL traits rely seems to do exactly that. And I appreciate the abstractions and compiler checks – if I wanted to just throw anything at ports and hope it's legal, I could just write C;)

@Branan, I have your blog posts about Teensy 3.2 bookmarked as reference:) and the parts about flashconfig / linker script was definitely useful (together with the linker script in @PaulStoffregen's cores repo) in figuring out the scaffolding. Thanks for writing that up! I'll definitely look at the 4.0 repo too – I can't find license info, though, so I'm not sure what am I allowed to do with it
 
I can't find license info, though, so I'm not sure what am I allowed to do with it

It looks like I never put a license on the Teensy 3.2 stuff either. Since at the moment both serve mostly as examples for how to build various types of abstractions, and thus are primarily valuable as a teaching tool, I'm happy to license them permissively to avoid any questions about cribbing concepts or code snippets. Consider them dual-licensed as Apache/MIT (as is pretty standard in the Rust community). I'll update the repos with actual license files when I have time.

If I build something that's made to be actually valuable as a piece of reusable software, on which real applications should be built, I'll likely use a copyleft license of some sort.

In the peripheral crate I have safe methods for whatever's described in the SVD as legal, and I can fall back to unsafely writing anything.

That's kind of a false sense of security - there are all sorts of invariants for a given peripheral that SVD can't express (it's not legal to set the baud rate of the UART on a teensy 4 when either TX or RX are enabled, for example). There are other peripherals where any access at all is probably unsafe without a lot of type-level or runtime checking into what's going on - MPUs, watchdogs, and DMA engines are all good examples here. I'm pretty convinced the only reasonable abstraction for "safety" is at the level of an entire peripheral, in order to contain and enforce those invariants; registers are innately unsafe in isolation. Strictly speaking this isn't "memory safety" as Rust defines it, but it feels weird to call an API "safe" when it has lots of invariants, some of which can crash the entire MCU if violated. This is broadly a philosophical question about what the roll of type and memory safety in a language like Rust is.

In addition to the safety question, most SVD files also just don't provide enough context for good bindings - having enum variants called _0 and _1 doesn't make code any more readable (and sometimes less readable), and frequently will still require comments indicating what the values mean and where they come from in the datasheet.

As a hobbyist I'm more interested in exploring how to use Rust's strengths to make embedded development better than in getting an MCU/Driver crate done quickly, so I just don't get much value out of using the rust-embedded stuff vs. spending a bit of time to build it from scratch for exactly what I need. YMMV
 
Last edited:
Status
Not open for further replies.
Back
Top