Topics:

  • Binary serialization for bootloader protocols.
  • Rust and WASM in a Chrome extension
  • Elixer and Rust and OpenCL
  • Command line argument processing
  • Handy tools like clippy, cargo-expand, cargo

Binary Serialization via @jacobrosenthal

Im trying to simplify uploading code to the pygamer. Theres an existing uf2 converter and you can just manually copy the file to the disk, but its kind of a lot of lines of setup and libraries we need installed. Honestly it works fine, I just like the idea of learning some more about Rust while cleaning up the workflow. This is the current state of things

#only needed one time
cargo install --git https://github.com/sajattack/uf2conv-rs
cargo install cargo-binutils
rustup component add llvm-tools-preview
#each compile, objcopy actually already calls cargo under the hood here saving 1 line
cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin
uf2conv-rs ferris_img.bin --base 0x4000 --output ferris_img.uf2
cp ferris_img.uf2 /Volumes/PYGAMERBOOT/

But its a lot of dependencies. Instead talking to the device over the HID protocol using the microsoft bootloader protocol called hf2 which adafruit uses heavily. My work is at uf2-rs where I use live upload over usb instead of a copy

cargo install --git https://github.com/jacobrosenthal/uf2
cargo install cargo-binutils
rustup component add llvm-tools-preview
cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin
uf2 flash -f ferris.bin 0x4000

My next step not yet done yet is to turn it into a cargo command using cargo-project reducing it into something like:

cargo install --git https://github.com/jacobrosenthal/uf2
cargo install cargo-binutils
rustup component add llvm-tools-preview
cargo uf2 --example ferris_img --release 0x4000

Ideally I can figure out how to convert the cargo elf file to a bin internally and remove the need for even llvm dependencies

cargo install --git https://github.com/jacobrosenthal/uf2
cargo uf2 --example ferris_img --release 0x4000

The big thing this library needs to solve is how binary serialization to turn this c style struct

pub(crate) struct Command {
    ///Command ID
    id: u32,
    ///arbitrary number set by the host, for example as sequence number. The response should repeat the tag.
    tag: u16,
    ///reserved bytes in the command should be sent as zero and ignored by the device
    _reserved0: u8,
    ///reserved bytes in the command should be sent as zero and ignored by the device
    _reserved1: u8,
}

into a buffer to send over the wire. In std rust you might use the std cursor and The core available from_le_bytes to help us turn bytes into primitives, and not mess up indexing, but but I like to write no_std code so I use a library called scroll like this

    //Packets are up to 64 bytes long
    let buffer = &mut [0_u8; 64];

    let mut offset = 1;

    buffer.gwrite_with(cmd.id, &mut offset, LE)?;
    buffer.gwrite_with(cmd.tag, &mut offset, LE)?;
    buffer.gwrite_with(cmd._reserved0, &mut offset, LE)?;
    buffer.gwrite_with(cmd._reserved1, &mut offset, LE)?;

Other topics discussed:

Using +nightly

For those not in the know say someone says you need the nightly build of a cargo crate. So you have to rustup default nightly first then cargo install blah-thing and then rustup default stable Instead you can add +nightly onto most commands to do something in nightly for just one command so all that becomes cargo +nightly install blah-thing

cargo-asm to see what assembly language is generated

cargo new helloworld && cd helloworld

$ cargo asm helloworld::main --rust
 fn main() {
 push    rbp
 mov     rbp, rsp
 sub     rsp, 48
     lea     rax, [rip, +, l___unnamed_2]
     mov     qword, ptr, [rbp, -, 48], rax
     mov     qword, ptr, [rbp, -, 40], 1
     mov     qword, ptr, [rbp, -, 32], 0
     lea     rax, [rip, +, l___unnamed_3]
     mov     qword, ptr, [rbp, -, 16], rax
     mov     qword, ptr, [rbp, -, 8], 0
     lea     rdi, [rbp, -, 48]
 println!("Hello, world!");
 call    std::io::stdio::_print
 }
 add     rsp, 48
 pop     rbp
 ret

cargo-expand to expand macros to the console so you can see what you're generating.

You may have seen by now you can implement Debug on a type for free if underlying types support it by using the #[derive(Debug)] attribute macro. Using cargo expand we can see what that did for us:

#[derive(Debug)]
struct S;

fn main() {
}
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::v1::*;
#[macro_use]
extern crate std as std;
struct S;
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::fmt::Debug for S {
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match *self {
            S => {
                let mut debug_trait_builder = f.debug_tuple("S");
                debug_trait_builder.finish()
            }
        }
    }
}

fn main() { }

cargo clippy for more help after it compiles

The compiler makes sure things compile, clippy tries to tell you there might be a better way to do things. You can replace 'check' in your ide with 'clippy' or run it on the command line.

$ cargo clippy
warning: casting u32 to f64 may become silently lossy if you later change the type
   --> src/bin.rs:166:51
    |
166 |     let padded_num_pages = (binary.len() as f64 / bininfo.flash_page_size as f64).ceil() as u32;
    |                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `f64::from(bininfo.flash_page_size)`
    |
    = note: `#[warn(clippy::cast_lossless)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless

    Finished dev [unoptimized + debuginfo] target(s) in 12.93s
$

Note you have it installed on nightly, it can also apply the suggestion automatically.

$ cargo +nightly fix -Z unstable-options --clippy
$ cargo clippy
    Finished dev [unoptimized + debuginfo] target(s) in 0.57s
$

There is a list of ALL the Clippy Lints which shows you all the rules in one place.

CLI Apps with clap.rs via @danielbank

Demo Repo

https://github.com/danielbank/next-prime-rs

Builder Pattern for Making a CLI

let matches = App::new("next-prime")
        .version("1.0")
        .about("Calculate the next prime after a given integer")
        .author("Daniel Bank")
        .arg(
            Arg::with_name("number")
                .help("a number after which the next prime will occur")
                .index(1)
                .required(true),
        )
        .get_matches();

Crates You Should Know

  • Clap - Command Line Argument Parser for Rust

  • Primes - A prime generator for Rust.

Learnings

Return Values

  • Forgot the return type (-> u64)

  • I was trying to return using a statement: n; vs an expression: n

Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.

error[E0308]: mismatched types
 --> src/main.rs:4:26
  |
4 | fn next_prime(x: u64) -> u64 {
  |    ----------            ^^^ expected u64, found ()
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
...
7 |     n;
  |      - help: consider removing this semicolon
  |
  = note: expected type `u64`
             found type `()`