Rustコンパイラ組込みのlintについて調べる

はじめに

Rustコンパイラであるrustcには組込みのlintがあります。 Rustのlintツールでは、clippyが有名ですが、rustc組込みの方もソースコードの改善に役立ちます。

7月の技術書同人誌博覧会のネタ探しの一環だったのですが、思ったより世の中の役に立ちそうだったので、ドラフトを公開します。 原稿落とさなければ、「組込み / ベアメタルRustテクニック集」的な薄い本を出す予定です。

ちなみにここに書いている内容は、全てThe rustc bookに書いてあります。

lint

lintソースコードコンパイラより厳密なルールに則り、検査するためのツールです。 Rustコンパイラには、様々なlintルールが組み込まれています。 ソースコードコンパイルする時、自動的にlintによる検査が行われます。

プロジェクトの運用ルールに合わせて、適切なlintルールを設定することで、ソースコードの品質をより向上できるでしょう。

lintレベル

rustcのlintレベルは、4つに分類されます。

  1. allow (許可)
  2. warn (警告)
  3. deny (拒絶)
  4. forbid (禁止)

各lintルールには、デフォルトのlintレベルがあり、コンパイルオプションかアトリビュートで上書きできるようになっています。 まず、lintレベルについて説明します。

allow (許可)

lintルールを適用しません。 例えば、次のコードをコンパイルしても、警告は発生しません。

pub fn foo() {}
$ rustc lib.rs --crate-type=lib

しかし、このコードはmissing_docsルールを違反しています。 lintレベルを上書きしてコンパイルすると、コンパイルエラーになったり、警告が出力されるようになります。

warn (警告)

lintルール違反があった場合、警告を表示します。

fn main() {
    let x = 5;
}

このコードは次の警告が報告されます。

warning: unused variable: `x`
 --> src/main.rs:2:9
  |
2 |     let x = 5;
  |         ^ help: consider prefixing with an underscore: `_x`
  |
  = note: #[warn(unused_variables)] on by default

deny (拒絶)

lintルール違反があった場合、コンパイルエラーになります。

fn main() {
    100u8 << 10;
}

このコードは、exceeding_bitshiftsルールに違反しており、コンパイルエラーになります。

error: attempt to shift left with overflow
 --> src/main.rs:2:5
  |
2 |     100u8 << 10;
  |     ^^^^^^^^^^^
  |
  = note: #[deny(exceeding_bitshifts)] on by default

forbid (禁止)

lintルール違反があった場合、コンパイルエラーになります。 forbidは、denyより強いレベルで、上書きができません。

下のコードは、アトリビュートmissing_docsルールをallowに上書きしています。

#![allow(missing_docs)]
pub fn foo() {}

missing_dogsルールを、denyレベルに設定してコンパイルすると、このコードはコンパイルできます。

$ rustc lib.rs --crate-type=lib -D missing-docs

一方、forbidレベルに設定してコンパイルすると、コンパイルエラーになります。

$ rustc lib.rs --crate-type=lib -F missing-docs
error[E0453]: allow(missing_docs) overruled by outer forbid(missing_docs)
 --> lib.rs:1:10
  |
1 | #![allow(missing_docs)]
  |          ^^^^^^^^^^^^ overruled by previous forbid
  |
  = note: `forbid` lint level was set on command line

lintレベルの設定方法

コンパイラフラグで設定

コンパイルオプションで、-A, -W, -D, -Fのいずれかを指定して、lintレベルを設定できます。

$ rustc lib.rs --crate-type=lib -W missing-docs

もちろん、複数のフラグを同時に設定することも可能です。

$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables

Cargoの設定ファイル内で、lintレベルを設定することも可能です。

$ cat .cargo/config
[build]
rustflags = ["-D", "unsafe-code"]

アトリビュートで設定

ソースコード内のアトリビュートで、allow, warn, deny, forbidのいずれかを指定して、lintレベルを設定できます。

$ cat lib.rs
#![warn(missing_docs)]

pub fn foo() {}

1つのアトリビュートに、複数のlintルールを指定できます。

#![warn(missing_docs, unused_variables)]

fn main() {
pub fn foo() {}
}

複数のアトリビュートを組み合わせて使うこともできます。

#![warn(missing_docs)]
#![deny(unused_variables)]

pub fn foo() {}

lintルール

次のコマンドでlintルールと、デフォルトレベルの一覧が取得できます。

$ rustc -W help

デフォルトレベルごとに、サンプルコード付きでlintルールが説明されています。

知られざるlintルールたち

ちらっと目について、へー、と思ったルールを独断と偏見で紹介します。

ちょっとマニアック

自分が知らんかった枠

RustのRTFM (Real Time For the Masses)を試してみる⑤

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

サンプルコードをマクロ展開して心が折れそうです。 とりあえず、当面サンプルコードの表面をなぞります。

タスク

RTFMでは、割り込みと例外をハードウェアタスクとして扱います。 RTFMでは、ソフトウェアタスクも提供しています。

ソフトウェアタスクは、割り込みハンドラから、優先度を指定してディスパッチできます。 ソフトウェアタスクをディスパッチする割り込みハンドラは、externブロックの中で宣言しておく必要があります。

ソフトウェアタスクになる関数は、taskアトリビュートをつけます。 ソフトウェアタスクをspawnする関数は、contextアトリビュートspawn引数をつけます。

#[app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init() {
        spawn.foo().unwrap();
    }

    #[task(spawn = [bar, baz])]
    fn foo() {
        hprintln!("foo").unwrap();

        // spawns `bar` onto the task scheduler
        // `foo` and `bar` have the same priority so `bar` will not run until
        // after `foo` terminates
        spawn.bar().unwrap();

        // spawns `baz` onto the task scheduler
        // `baz` has higher priority than `foo` so it immediately preempts `foo`
        spawn.baz().unwrap();
    }

    #[task]
    fn bar() {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[task(priority = 2)]
    fn baz() {
        hprintln!("baz").unwrap();
    }

    // Interrupt handlers used to dispatch software tasks
    extern "C" {
        fn UART0();
        fn UART1();
    }
};

spawnの結果は、Resultで返ってきます。 これは、タスクが起動できる数 (capacity) が指定できるようになっており、その数以上のタスクを起動できないからです。 capacityはデフォルトでは、1になっています。

    #[interrupt(spawn = [foo, bar])]
    fn UART0() {
        spawn.foo(0).unwrap();
        spawn.foo(1).unwrap();
        spawn.foo(2).unwrap();
        spawn.foo(3).unwrap();

        spawn.bar().unwrap();
        // runtime error
        spawn.bar().unwrap();
    }

    #[task(capacity = 4)]
    fn foo(x: u32) {
        hprintln!("foo({})", x).unwrap();
    }

    #[task]
    fn bar() {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

ソフトウェアタスクには、メッセージを渡すことができます。 メッセージは、タスクの引数として、シグネチャに含める必要があります。

#[app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init() {
        spawn.foo(/* no message */).unwrap();
    }

    #[task(spawn = [bar])]
    fn foo() {
        static mut COUNT: u32 = 0;

        hprintln!("foo").unwrap();

        spawn.bar(*COUNT).unwrap();
        *COUNT += 1;
    }

    #[task(spawn = [baz])]
    fn bar(x: u32) {
        hprintln!("bar({})", x).unwrap();

        spawn.baz(x + 1, x + 2).unwrap();
    }

    #[task(spawn = [foo])]
    fn baz(x: u32, y: u32) {
        hprintln!("baz({}, {})", x, y).unwrap();

        if x + y > 4 {
            debug::exit(debug::EXIT_SUCCESS);
        }

        spawn.foo().unwrap();
    }
}

fooのコードで、static mut COUNTを使用する際、デリファレンスしているのが気になりました。

    fn foo(c: foo::Context) {
        static mut COUNT: u32 = 0;
// ...
        c.spawn.bar(*COUNT).unwrap();
        *COUNT += 1;
    }

どうも、static mutを参照にマクロ展開しているようです。

次の手順でマクロ展開したコードを確認しました。

$ cargo rustc --example message -- -Z unstable-options --pretty=expanded > message_expanded.rs
# フォーマットしないとツラいです
$ rustfmt message_expanded.rs

fooは次のように展開されます。

#[allow(non_snake_case)]
fn foo(__locals: foo::Locals, c: foo::Context) {
    use rtfm::Mutex as _;
    let COUNT = __locals.COUNT;
    ::cortex_m_semihosting::export::hstdout_str("foo\n").unwrap();
    c.spawn.bar(*COUNT).unwrap();
    *COUNT += 1;
}

foo::Localsという型で引数を受け取っており、そこからCOUNTを取得しています。 foo::Localsは、次の通りです。

#[allow(non_snake_case)]
#[doc(hidden)]
pub struct fooLocals<'a> {
    COUNT: &'a mut u32,
    __marker__: core::marker::PhantomData<&'a mut ()>,
}

COUNTが参照になっていますね。

試しに、COUNTデリファレンスせずに使うと、次のコンパイルエラーになります。

error[E0308]: mismatched types
  --> examples/message.rs:25:21
   |
25 |         c.spawn.bar(COUNT).unwrap();
   |                     ^^^^^
   |                     |
   |                     expected u32, found &mut u32
   |                     help: consider dereferencing the borrow: `*COUNT`
   |
   = note: expected type `u32`
              found type `&mut u32`

いやー、知らないと意味がわからないですよ、これ。

RustのRTFM (Real Time For the Masses)を試してみる④

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

面白いので、引き続き触っていきます。

リソース管理続き

RTFMstatic変数は、通常のstatic変数と異なり、実行時に初期化できます。 このように実行時に初期化するリソースをlate resourcesと呼んでいます。 補足すると、lazy_staticとは、また異なる仕組みのようです。Optionで包んで云々、ということはやっていません。 リソースの初期化は、init関数で行う必要があり、init関数は、初期化したリソースを戻り値にしなければなりません。

何を言っているのかわからないと思うので、サンプルコードを見てみましょう。

use heapless::{
    consts::*,
    spsc::{Consumer, Producer, Queue},
};

#[app(device = lm3s6965)]
const APP: () = {
    // Late resources `()`を初期値にします
    static mut P: Producer<'static, u32, U4> = ();
    static mut C: Consumer<'static, u32, U4> = ();

    #[init]
    fn init() -> init::LateResources {
        // NOTE: we use `Option` here to work around the lack of
        // a stable `const` constructor
        static mut Q: Option<Queue<u32, U4>> = None;

        *Q = Some(Queue::new());
        let (p, c) = Q.as_mut().unwrap().split();

        // late resourcesを初期化して、関数の戻り値とします
        init::LateResources { P: p, C: c }
    }

    #[idle(resources = [C])]
    fn idle() -> ! {
        loop {
            if let Some(byte) = resources.C.dequeue() {
                hprintln!("received message: {}", byte).unwrap();

                debug::exit(debug::EXIT_SUCCESS);
            } else {
                rtfm::pend(Interrupt::UART0);
            }
        }
    }

    #[interrupt(resources = [P])]
    fn UART0() {
        resources.P.enqueue(42).unwrap();
    }
};

まず、()で初期化されたlate resourcesを宣言します。

    static mut P: Producer<'static, u32, U4> = ();
    static mut C: Consumer<'static, u32, U4> = ();

次に、init関数のシグネチャが、init::LateResourcesを返すものに変わります。 このinit::LateResourcesは、late resourcesPCとのタプルです。

    #[init]
    fn init() -> init::LateResources {

idleはC、UART0はPを、それぞれ専有して利用するため、ロックなしでリソースを利用することができます

このサンプルでは、`heaphessクレートのqueueが使われています。 このheaplessクレートは、文字通り、ヒープがない環境でコレクションを使うためのクレートです。 queueは、single producer / single consumerのモデルになっています。 このqueueの初期化をinitで行い、ProducerとConsumerのオブジェクトを共有リソースとして管理することで、うまいこと安全な機構になっているように見えます。

リソースがただのstaticで、可変参照にならない場合も、ロックなしでリソースにアクセスできます。 これは、late resourcesにも適用されます。

#[app(device = lm3s6965)]
const APP: () = {
    static KEY: u32 = ();

    #[init]
    fn init() -> init::LateResources {
        rtfm::pend(Interrupt::UART0);
        rtfm::pend(Interrupt::UART1);

        init::LateResources { KEY: 0xdeadbeef }
    }

    #[interrupt(resources = [KEY])]
    fn UART0() {
        hprintln!("UART0(KEY = {:#x})", resources.KEY).unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[interrupt(priority = 2, resources = [KEY])]
    fn UART1() {
        hprintln!("UART1(KEY = {:#x})", resources.KEY).unwrap();
    }
};

RustのRTFM (Real Time For the Masses)を試してみる③

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

軽い気持ちで触り始めたのですが、読み進めるうちに、手続きマクロでゴリゴリ静的解析している変態実装であることがわかってきました。 少しずつフレームワークの実装にも突っ込んでいきたいですね。

リソース管理

cortex-m-rtクレートを使用する場合、割り込みとメイン関数間でのデータ共有に制限があります。 常に、全ての割り込みを無効化するcortex_m::interrupt::Mutexを使わなければなりません。 しかし、全ての割り込みを無効化することは、常に求められる条件ではありません。

例えば、2つの割り込みハンドラがデータを共有する場合、両者の優先度が同じで、プリエンプションが発生しないとすると、ロックは不要です。 RTFMでは、ソースコードを静的に解析することで、不要なロックをせずに、共有データにアクセスできるようになっています。 このような解析が可能な理由は、appアトリビュート内にアプリケーションの実装を、全て書くためです。

また、RTFMでは、動的な割り込み優先度の変更をサポートしていません。 そのため、全ての割り込みハンドラ間の優先度は静的に決定します。 これがうまいこと生きており、ある共有データを使用する割り込みハンドラ同士で、最も優先度の高いハンドラはロックを取得しなくても共有データにアクセスできます。

そろそろサンプルコードを見てみましょう。 共有データは、static変数として宣言します。

#[app(device = lm3s6965)]
const APP: () = {
    // A resource
    static mut SHARED: u32 = 0;

    #[init]
    fn init() {
        rtfm::pend(Interrupt::UART0);
        rtfm::pend(Interrupt::UART1);
    }

    #[idle]
    fn idle() -> ! {
        debug::exit(debug::EXIT_SUCCESS);

        // error: `SHARED` can't be accessed from this context
        // SHARED += 1;

        loop {}
    }

    // `SHARED` can be access from this context
    #[interrupt(resources = [SHARED])]
    fn UART0() {
        *resources.SHARED += 1;

        hprintln!("UART0: SHARED = {}", resources.SHARED).unwrap();
    }

    // `SHARED` can be access from this context
    #[interrupt(resources = [SHARED])]
    fn UART1() {
        *resources.SHARED += 1;

        hprintln!("UART1: SHARED = {}", resources.SHARED).unwrap();
    }
};

共有データにアクセスする関数は、アトリビュート内でresources = [<変数名>]を引数にします。 ここで、2つの割り込みハンドラUART0UART1は共に共有データSHAREDを使用します。 2つの割り込みは、割り込み優先度が同じで、プリエンプションも発生しません。 そのため、2つの割り込みハンドラでは、Mutexなしで共有データにアクセスできます。

割り込み優先度を設定する場合、同じ共有データを使用する割り込みハンドラのうち、優先度が最も高いハンドラだけはロックが不要です。

//! examples/lock.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    static mut SHARED: u32 = 0;

    #[init]
    fn init(_: init::Context) {
        rtfm::pend(Interrupt::GPIOA);
    }

    // when omitted priority is assumed to be `1`
    #[interrupt(resources = [SHARED])]
    fn GPIOA(mut c: GPIOA::Context) {
        hprintln!("A").unwrap();

        // the lower priority task requires a critical section to access the data
        c.resources.SHARED.lock(|shared| {
            // data can only be modified within this critical section (closure)
            *shared += 1;

            // GPIOB will *not* run right now due to the critical section
            rtfm::pend(Interrupt::GPIOB);

            hprintln!("B - SHARED = {}", *shared).unwrap();

            // GPIOC does not contend for `SHARED` so it's allowed to run now
            rtfm::pend(Interrupt::GPIOC);
        });

        // critical section is over: GPIOB can now start

        hprintln!("E").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[interrupt(priority = 2, resources = [SHARED])]
    fn GPIOB(mut c: GPIOB::Context) {
        // the higher priority task does *not* need a critical section
        *c.resources.SHARED += 1;

        hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap();
    }

    #[interrupt(priority = 3)]
    fn GPIOC(_: GPIOC::Context) {
        hprintln!("C").unwrap();
    }
};

ドキュメントによると、このプログラムの実行結果は、次のようになるようです。

$ cargo run --example lock
A
B - SHARED = 1
C
D - SHARED = 2
E

note: 手元の環境で実行すると、次の結果になります。要調査。レポジトリのCIは通っているようなので、手元環境の問題?

A D - SHARED = 2 B - SHARED = 2 C E

GPIOAの共有データアクセスは、クリティカルセクションのMutexを取得しており、割り込み禁止状態で行われます。

少し上記プログラムを改変して、どうなるか試してみます。

const APP: () = {
    static mut SHARED: u32 = 0;

// ...

    #[interrupt(resources = [SHARED])]
    fn GPIOA() {
        hprintln!("A").unwrap();

        // GPIOBと同じ方法でアクセスしてみます
        *c.resources.SHARED += 1;

        hprintln!("E").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[interrupt(priority = 2, resources = [SHARED])]
    fn GPIOB(mut c: GPIOB::Context) {
        *c.resources.SHARED += 1;

        hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap();
    }

// ...
};

ビルドすると次のコンパイルエラーになります。

$ cargo run --example lock
error[E0614]: type `resources::SHARED<'_>` cannot be dereferenced
  --> examples/lock.rs:41:9
   |
41 |         *c.resources.SHARED += 1;
   |         ^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0614`.
error: Could not compile `cortex-m-rtfm`.

To learn more, run the command again with --verbose.

どうも無名の型になっているようで、詳細がつかみにくいですが、GPIOA()GPIOB()とで、resource::SHARED`の型が違う、ということがわかります。

RustのRTFM (Real Time For the Masses)を試してみる②

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

前回変なところで切ってしまったため、今回は軽めです。

idleタスク

idleタスクは、initタスクの後に実行することができます。 initタスクと異なり、割り込みが有効化された状態で実行します。

関数シグネチャ[unsafe] fn() - > !であり、関数が戻ることは許されません。

下記のようにプログラムを書くことで、initの後にidleを実行します。

#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        hprintln!("init").unwrap();
    }

    #[idle]
    fn idle() -> ! {
        static mut X: u32 = 0;

        // Safe access to local `static mut` variable
        let _x: &'static mut u32 = X;

        hprintln!("idle").unwrap();

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }
};

interrupt / exception

cortex-m-rtクレートを利用する時と同じ方法で、interruptexceptionを使うことができます。

#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        // Pends the UART0 interrupt but its handler won't run until *after*
        // `init` returns because interrupts are disabled
        rtfm::pend(Interrupt::UART0);

        hprintln!("init").unwrap();
    }

    #[idle]
    fn idle() -> ! {
        // interrupts are enabled again; the `UART0` handler runs at this point

        hprintln!("idle").unwrap();

        rtfm::pend(Interrupt::UART0);

        debug::exit(debug::EXIT_SUCCESS);

        loop {}
    }

    #[interrupt]
    fn UART0() {
        static mut TIMES: u32 = 0;

        // Safe access to local `static mut` variable
        *TIMES += 1;

        hprintln!(
            "UART0 called {} time{}",
            *TIMES,
            if *TIMES > 1 { "s" } else { "" }
        )
        .unwrap();
    }
};

このプログラムを実行すると、2回割り込みをpendしているので、UART0が2回呼ばれます。

実行結果

$ cargo run --example interrupt
init
UART0 called 1 time
idle
UART0 called 2 times

RustのRTFM (Real Time For the Masses)を試してみる①

はじめに

組込みRust界の伝説japaric氏が実装しているReal Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollersを試してみます。

github.com

RTFM自体は、Real-time for the masses, step 1: Programming API and static priority SRP kernel primitives.という論文が大本のようです。

ドキュメントもしっかり整備されています。見習わないといけないですね。

japaric.github.io

RTFM brief

ドキュメントの機能一覧を抜粋します。

  • タスク管理
  • タスク間メッセージ
  • タイマキュー
  • プリエンプティブマルチタスキング
  • 効率的でデータ競合のないメモリ共有
  • デッドロックしない実行
  • 最小限のスケジューリングオーバーヘッド
  • 極めて効率的なメモリ使用

最小限のリアルタイムコアシステム、といった印象を受けます。

initサンプル

qemu-system-armがあれば、動かせるサンプルが多く用意されています。 QEMUをターゲットに、少しサンプルを動かしてみましょう。

$ git clone https://github.com/japaric/cortex-m-rtfm.git
$ cd cortex-m-rtfm
$ cargo run --example init
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/debug/examples/init`
init # サンプルで出力した文字列

単純に文字を出力するだけのサンプルのようです。 アプリケーションのソースコードを見てみましょう。

ソースコード

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_semihosting;

use cortex_m_semihosting::{debug, hprintln};
use rtfm::app;

#[app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init() {
        static mut X: u32 = 0;

        // Cortex-M peripherals
        let _core: rtfm::Peripherals = core;

        // Device specific peripherals
        let _device: lm3s6965::Peripherals = device;

        // Safe access to local `static mut` variable
        let _x: &'static mut u32 = X;

        hprintln!("init").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }
};

まず、良いなと思ったのは、最初に#![deny(unsafe_code)]しています。 組込みRust普及活動をする上で、unsafeだらけのコードになるのでは?という質問を良く受けますが、アプリケーションレベルでは、unsafeを許さないこのアトリビュートを設定できます。 後は、それだけこのRTFMフレームワークが上手にunsafeを閉じ込めた実装になっている、というのもこの1行からわかります。

見慣れないのは、#[app(device = lm3s6965)]アトリビュートと、#[init]アトリビュートです。

#[app(..)]アトリビュートは、device引数を使って、svd2rustで生成されたperipheral access crate (PAC)を指すパスを指定します。 #[app(..)]アトリビュートは、const itemに適用しなければなりません。これは、nightlyを使わないようにするための制限です。

appアトリビュートは、initアトリビュートでマーキングされた初期化関数があることを想定します。 initアトリビュートは、[unsafe] fn()シグネチャに持つ関数につけることができます。

init関数は、アプリケーションとして実行される最初の関数です。 この関数は、割り込み禁止状態で実行します。

coredeviceという変数があり、この変数を通して、Cortex-Mとペリフェラルにアクセスできます。

        // Cortex-M peripherals
        let _core: rtfm::Peripherals = core;

        // Device specific peripherals
        let _device: lm3s6965::Peripherals = device;

appinitアトリビュートあたりがマクロ展開されて定義されるのでしょう。

mdbook-epubお試し

はじめに

同人誌を書く環境を模索しています。 Rustが好きなので、Rust製のドキュメントビルダーmdbookも試してみます。

mdbook自体には、PDF出力機能がないため、3rd party pluginであるdbook-epubを試してみます。

インストール

Rust環境はあらかじめ構築しておいて下さい。

cargo install mdbook
cargo install mdbook-epub

mdbookプロジェクトのbook.tomlに次の一行を追加します。

[output.epub]

通常通り、mdbookを呼び出します。

$ mdbook build
2019-05-02 20:51:53 [INFO] (mdbook::book): Book building has started
2019-05-02 20:51:53 [INFO] (mdbook::book): Running the epub backend
2019-05-02 20:51:53 [INFO] (mdbook::renderer): Invoking the "epub" renderer
$ ls book
'The Embedonomicon.epub'

htmlではなく、epubが出力されています。

読んでみる

calibreというepubリーダー / 編集ソフトを使って、少しCSSをいじりながら、調節してみます。

sudo apt install calibre

f:id:tomo-wait-for-it-yuki:20190503060606p:plain
mdbook-epub

意外と悪くない…?