🐼

Rustのno_std環境でStringを使う

2022/04/11に公開

経緯

STM32マイコンでRustを勉強をしててUART出力できるようになった際に、no_std環境で可変文字列(String)を扱いたくなって調査したので備忘録

確認環境

バージョン
rustc rustc 1.62.0-nightly (8bf93e9b6 2022-04-09)

方法

allocの実装が必要。
そのために以下手順を実施

No 実施内容
1 Nightly Rustへの切り替え
2 #[global_allocator]アトリビュート指定オブジェクトの宣言
3 #[alloc_error_handler]アトリビュート指定の関数実装
4 Stringのuse宣言追加

Nightly Rustへの切り替え

必須ではないが手順2手順3のために基本的に必要。ないとビルドエラーになる。

ターミナルで以下実施

rustup override set nightly

global_allocatorアトリビュート指定オブジェクトの宣言

アプリのどこかに#[lobal_allocator]アトリビュートを指定したグローバル変数を宣言する必要がある。
この変数の型はGlobalAllocトレイトを実装している必要がある。
自前で実装してもよいが、ここでは公開されているalloc-cortex-mを使用。

alloc-cortex-m使用例

3手順に分けて記載

  1. Cargo.tomlのdependenciesに追加
Cargo.toml
[dependencies]
alloc-cortex-m = "0.4.2"
  1. アプリ内にグローバル変数宣言
use alloc_cortex_m::CortexMHeap;
#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();
  1. アプリ開始時に初期化
#[entry]
fn main() -> ! {
    {
        use core::mem::MaybeUninit;
        const HEAP_SIZE: usize = 1024*20; //20KBの領域
        static mut HEAP: [MaybeUninit<u8>; HEAP_SIZE] = 
		[MaybeUninit::uninit(); HEAP_SIZE];
        unsafe { ALLOCATOR.init(HEAP.as_ptr() as usize, HEAP_SIZE) }
    }

alloc_error_handlerアトリビュート指定の関数実装

以下2つ必要

  1. #![feature(alloc_error_handler)]をルートモジュール先頭に追加
    ルートモジュール先頭でないとビルドエラーになる
main.rs
#![no_std]
#![no_main]
#![feature(alloc_error_handler)]
  1. 関数実装
    アプリ内に関数を実装する
use core::alloc::Layout;
#[alloc_error_handler]
fn oom(_: Layout) -> ! {
    loop {}
}

Stringのuse宣言追加

アプリでStringを使用するためにuseが必要。
他にもto_string()関数やformat()マクロなどを使う際にも都度必要。
おそらく一纏めにするor記載省略する方法があるが、ここでは明示的に記載。

use alloc::string::String;
use alloc::string::ToString;
use alloc::format;

fn test() {
    let base_string = String::from("aiueo:"); //Stringのuse必要
    let mut count:u32 = 0;
    loop {
        let custom_string = format!("{}{}{}", //formatのuse必要
            base_string, count.to_string(),"\n"); //ToStringのuse必要

        //custom_stringを使ったUART送信処理
        ...

        count=count+1;
    }
}

備考

Nightly Rustを使わない方法

手順2手順3に対して以下実施によりStable Rustでもalloc実装可能

  • 手順2(global_allocatorアトリビュート指定オブジェクトの宣言)に対して
    Nightly Rustを要求しないアロケータを使用する (alloc-cortex-mはNG)
    または自前でGlobalAllocトレイトを実装する。
    自前で実装する場合は参考1.を参照

  • 手順3(alloc_error_handlerアトリビュート指定の関数実装)に対して
    nightly-crimesを使用してalloc_error_handler関係を囲む。
Cargo.toml
[dependencies]
nightly-crimes = "1.0.2"
main.rs
#![no_std]
#![no_main]
use nightly_crimes::nightly_crimes;
nightly_crimes! {
    #![feature(alloc_error_handler)]
    use core::alloc::Layout;
    #[alloc_error_handler]
    fn oom(_: Layout) -> ! {
        loop {}
    }
}

参考

https://tomoyuki-nakabayashi.github.io/embedded-rust-techniques/03-bare-metal/allocator.html

https://zenn.dev/fraternite/articles/bc9c99658aae1c

Discussion