並行処理(@Rust)

Rustももちろん並行処理ができて、そのロジックは他の言語と類似ですが、簡単なコードで確認。Rustのドキュメンtの並行処理を多少改変しています。

どこが改変かというと、スレッドを複数spawnした時の終了待ちをhandleを配列(handles)に入れて、配列からhandleを取り出して全ての終了を待つようにしています。GolangのWaitGroupに相当する機能はなさそうなので、

use std::thread;
use std::time::Duration;

fn main() {
    let mut handles = Vec::new();
    for i in 0..3 {
        let handle = thread::spawn(move || {
            for j in 1..10 {
                println!("hi number {} {} from the spawned thread!", i, j);
                thread::sleep(Duration::from_millis(1));
            }
        });
        handles.push(handle);
    }

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

<実行結果>

hi number 1 1 from the spawned thread!
hi number 2 1 from the spawned thread!
hi number 1 from the main thread!
hi number 0 1 from the spawned thread!
hi number 1 2 from the spawned thread!
hi number 2 2 from the spawned thread!
hi number 2 from the main thread!
hi number 0 2 from the spawned thread!
hi number 1 3 from the spawned thread!
hi number 2 3 from the spawned thread!
hi number 0 3 from the spawned thread!
hi number 3 from the main thread!
hi number 1 4 from the spawned thread!
hi number 2 4 from the spawned thread!
hi number 0 4 from the spawned thread!
hi number 4 from the main thread!
hi number 1 5 from the spawned thread!
hi number 2 5 from the spawned thread!
hi number 0 5 from the spawned thread!
hi number 1 6 from the spawned thread!
hi number 0 6 from the spawned thread!
hi number 2 6 from the spawned thread!
hi number 0 7 from the spawned thread!
hi number 1 7 from the spawned thread!
hi number 2 7 from the spawned thread!
hi number 0 8 from the spawned thread!
hi number 1 8 from the spawned thread!
hi number 2 8 from the spawned thread!
hi number 0 9 from the spawned thread!
hi number 1 9 from the spawned thread!
hi number 2 9 from the spawned thread!

二重ループ処理の複数(三個)のスレッドが動作しています。もちろんスレッドの実行順序は指定できません。

 

admin

 

 

Rustのテキスト

Rustの場合、コミュニティがしっかりしていてドキュメントの整備もきちんとされているので、特別にテキストを購入する必要はなさそうです。

https://doc.rust-jp.rs/book-ja/title-page.html

の15章あたりまで、ダラダラと読んでますが、例は

https://doc.rust-jp.rs/rust-by-example-ja/

こちらを使えば、一通りの理解はできるように思います。

結局のところ、Rustの仕様のかなりの部分はメモリ管理をコンパイラが判断できるようにユーザーが記述するコードで指示することに集約されるだろうと思う。したがって堅牢性と実行速度を要求するOSやアプリケーションにはRustを使うと言う選択になるんだろう。組み込みやIoTなどで小規模な開発では、コードが冗長になりがちなので、Rustである必要性はあるのかと言う感覚はあります。

 

admin

構造体

GolangやRustでは積極的に構造体を使い、また構造体を効率的に使うための言語仕様も用意されていますが、その背景は必要なデータセットは構造体にまとめることで、読みやすく従ってバグも入りづらいコードを書くことにあるだろうと思います。

同じ機能をGolangとRustで記述してみます。

<Golang>

type Rectangle struct {
  length  float64
  breadth float64
}

func (r Rectangle) area() float64 {
  return r.length * r.breadth
}

<Rust>

struct Rectangle {
    length: f64,
    breadt: f64,
  }
impl  Rectangle{
  fn area(&self) -> f64 {
    return &self.length * &self.breadt
  }
}

Rust呼び出し方、

  fn main() {
    let mut rec = Rectangle{length:0.0,breadt:0.0};

    rec.length = 20.0;
    rec.breadt = 30.0;

    print!("{}", rec.area())
}

Golangでは

(r Rectangle)

を使って構造体と結びつけ、Rustでは

impl Rectangle

でインターフェースの如くimplementでメソッドを定義していますが、上記二種の結果は全く同じ出力をするし、記法にも大差ありません。c++ではこのような記法はないので、現代の言語の特徴と言ってもいいのではないかと思います。

 

admin

関数(メソッド)ごとに文字列と文字列スライスは意識しないといけない(@Rust)

Rustの文字型にはString(可変長)、文字列スライス(固定長)、char型(1バイト)の三種類がありますが、関数やメソッドによって引数や適用が変わってきます。

以下は文字列を全て小文字に変換する関数to_lowercase()の例ですが、操作対象は文字列スライスでなければ機能しませんし、出力はString型になります。

VScodeだと、型は自動で補完してくれますね。

fn main() {
    let text = "heLLO worLd";
    let result: String = text.to_lowercase();
    let search= "worl";

    if result.contains(search){
        println!("{} and {}", result, search);
    }

以下は関数(メソッド)のAPIの記述です、

何故、操作対象は文字列スライス型でなければならないかですが、おそらく変更がなくて参照するだけだろうからだと思います。処理量の観点でも参照の方が少なくて済むだろうし。

 

admin

 

ラズパイPico WのRust

ラズパイPico WのLチカが動かないなと思って、専用のファームがあるのかとも思ったけど、そんな理屈はない。MicroPythonはインタプリタだから実行環境必要だけど、Rustは実行ファイルになっているからそのまま実行出来るはず。

で実はHALがPicoとは別物(embassy)で、オンボードのLEDは無線モジュールから出力されている。MycroPythonやArduino IDEではPico/Pico Wのハードの違いを開発環境側で吸収しているんだろう。

この辺の記述は以下のリンクが参考になります。

https://qiita.com/Azi/items/422c654bb476e0abf118

LEDブリンクのソースコードは、

https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/wifi_blinky.rs

になります。Pico W用のHALは今現在も活発にアップデートされているので、安定的に使えるようになるのは今少し時間が必要です。それまではMycroPython/Arduino IDE/VS code環境で使うのが良さそうです。

 

admin

Rustで特徴的と思ったところ(その1)

Rustのオンラインブック、

https://doc.rust-jp.rs/book-ja/title-page.html

を読み始めて、第5章までの分です。自分の他言語経験から見てユニークと思えるところを、並びは時系列です。特にC/C++などで起こるメモリ管理上のバグの作り込みを防ぐためのメモリ管理機能(所有権)がいちばん特徴的だろうと思う。

・crate : クレートはRustソースコードを集めたものである、バイナリ形式でもそういうけども

・Cargo.toml : Tom’s Obvious, Minimal Language、パッケージのリストと版数を指定するテキストファイル

・Cargo : Rustのビルドシステム兼パッケージマネージャ

 cargo checkでコンパイルエラーがチェックできる

・println! : !はマクロの意味、簡易に結果出力で使用で構造体ではコンパイルエラー、回避方法(20行ぐらい下)はありますが

・関連関数

String::new() :newは関連関数で、Stringインスタンス作成する

・参照変数のmutable化

io::stdin().read_line(&mut guess) : &mutは参照変数をミュータブルにする、記法はmut &guessではない

・Cargo.lockファイル : ビルドの再現性確保のためにクレートのバージョンを保存しておく、自動生成されユーザがいじるファイルでは無い

・traitはデータ型を分類する仕組み、crateの要素(任意の型となりうるSelfに対して定義されたメソッドの集合のこと)、類型的にはJavaのinterfaceのようなもの

・Shadowing : 前の値を新しい値で覆い隠す(shadowする)ことが許されている、型は違っていても同じでも良い

・タプルの要素は型が違っても大丈夫

let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0; // タプル要素へのダイレクトアクセス

・Rustの配列は固定長、ベクター型は可変長

    let a: [i32: 5] = [1, 2, 3, 4, 5];

・戻り値の指定方法は;をつけてはいけない、-> は戻り値の型を指定

fn five() -> i32 {
        5 // it’s formula not statement
}         // return value

・条件式を右辺に記述できる

let number = if condition { 5 } else { 6 }; // possible if both type is same

・所有権:これはRustのコア機能(メモリ管理がRustの一大機能)、本質はヒープ領域の管理になりますが

・println!に指示する: 直接エンドユーザ向けの出力で、構造体はこれではダメで

#[derive(Debug)]行を追加必要

・メソッド記法は構造体の文脈で定義(impl Rectangle)される、Golangの構造体との関連付けに書式は違うが似てると思う

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 { // implしてるから&self、構造体インスタンスへの参照
        self.width * self.height
    }
}

・関連関数:implブロック内で定義される、selfを引数に取らない関数。構造体と関連づけられていないからメソッドではない、String::new()はその例。よくある使い方は構造体のインスタンスを返却する関数。

 

admin

やはりクロス環境はDockerが簡単(失敗編)

本命だろうと思うDockerでやってみる。以前に、

https://isehara-3lv.sakura.ne.jp/blog/2023/04/22/dockerでgolangのbuild環境を作る/

で作成しているDockerのコンテナ起動して、コンソールからRustインストールすると当たり前にコンパイルできる。crossを使わなくてcargoで普通に管理できるからやはりこれが本命。

ペリフェラルを使うためのライブラリは、

https://github.com/golemparts/rppal

を使うのが一般的のようなのでこれを使ってみます。基本のLチカというサンプルがあったので、GPIOを使ったサンプルプログラムをビルドしてみました。

https://misoji-engineer.com/archives/rust-led.html

このソースのままでmain.rsにコピーしてcargo buildでコンパイルが完了しました。

コンパイル前には、Cargo.tomlの[dependencies]にrppalの追加が必要です。Rustは必要なライブラリをソースコードとは別に定義ファイルで管理するようになっています。

# cat Cargo.toml
[package]
name = "led_blink"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rppal = "0.14.0"

初回はrppalのコンパイルも入るので多少時間はかかりますが、次回からは短縮されるはずです。

# cargo build
    Updating crates.io index
  Downloaded rppal v0.14.1
  Downloaded libc v0.2.147
  Downloaded 2 crates (766.8 KB) in 0.86s
   Compiling libc v0.2.147
   Compiling rppal v0.14.1
   Compiling led_blink v0.1.0 (/root/rust/led_blink)
    Finished dev [unoptimized + debuginfo] target(s) in 21.72s

P.S. (2023/8/5)

ラズパイzeroに転送して実行させると、

$ ./led_blink
Illegal instruction

と言われた、ラズパイzero上にRustをインストールしてビルドさせるとおよそ2分でビルド出来たから、実機ビルドでも取り敢えず許容範囲。

<実行させたコード:0.1秒ごとにハイ・ローを切り替え>

extern crate rppal;

use std::error::Error;
use std::thread;
use std::time::Duration;

use rppal::gpio::Gpio;

const GPIO_LED1: u8 = 17;
const GPIO_LED2: u8 = 27;

fn main() -> Result<(), Box> {

    let mut led1 = Gpio::new()?.get(GPIO_LED1)?.into_output();
    let mut led2 = Gpio::new()?.get(GPIO_LED2)?.into_output();

    // Blink the LED by setting the pin's logic level high for 1000 ms.
    loop{
        led2.set_high();
        led1.set_high();
        thread::sleep(Duration::from_millis(100));
        
        led1.set_low();
        led2.set_low();
        thread::sleep(Duration::from_millis(100));
    }
}

確認はピンの信号波形をオシロで観察、

 

admin

Linux用のクロスコンパイル

以下のサイトを参考にx86のLinux環境でのクロスはできた。

https://betterprogramming.pub/cross-compiling-rust-from-mac-to-linux-7fad5a454ab1

結構普通のやり方だと思うけど、brewでターゲット用のlinkerをインストールしているのが大事なところでしょう。ラズパイ用がbuild出来ないのもおそらくlinkerをインストールしていないからでしょう

% brew install SergioBenitez/osxct/x86_64-unknown-linux-gn

.cargo/config.toml を作成編集

and adding the linker for the target:

[target.x86_64-unknown-linux-gnu]
linker = "x86_64-unknown-linux-gnu-gcc"


% TARGET_CC=x86_64-unknown-linux-gnu cargo build --release --target x86_64-unknown-linux-gnu
   Compiling rust v0.1.0 (/Users/xxxxx/rust)
error[E0463]: can't find crate for `std`

=> 見覚えのあるエラーがやはりでる。

% rustup target add x86_64-unknown-linux-gnu

でライブラリインストールして解決。

% TARGET_CC=x86_64-unknown-linux-gnu cargo build --release --target x86_64-unknown-linux-gnu
   Compiling rust v0.1.0 (/Users/usamiryuuichi/rust)
    Finished release [optimized] target(s) in 1.60s

.cargo/config.tomlはディレクトリの親含めて検索するようですが、今回はプロジェクトのルートに作成。

クロスコンパイルなのでコンパイル時間はネーティブよりかかるのはGolangと同じです。Docker使ってもできるはず、というかラズパイ用のlinkerは同サイトには見えないのでDocker使うのが一番素直な感じがします。Golangでネーティブのgccを使わない限りにおいてはDockerは必要ないので、この点ではGolangのほうが使いやすさはあります。

動作はIntel MacのVMware上のUbuntuで確認しました。

 

admin

M1 MacでRustの環境

動かすのはラズパイでIoT的な物にしたいが、まずはMacで開始します。

Rust関連のインストール(やり方は公式ページなど参照)、

% rustc --version
rustc 1.71.0 (8ede3aae2 2023-07-12)

% cargo version
cargo 1.71.0 (cfd3bbd8f 2023-06-08)

% rustup --version	// Rust tool chain, cargo(Rust's package manager)とは併用
rustup 1.26.0 (5af9b9484 2023-04-05)

cargo-editはcargoの拡張機能、単独のコマンドではない
% cargo install cargo-edit

% cargo install cross

クロス環境用にcrossをインストールしますが。本来的にはcargoはRustのパッケージ管理とビルドツール。crossはクロスコンパイル環境ですが、実はcrossはDockerにターゲットDockerイメージを展開してコンパイルするようになっています。

Intel Mac用のコンパイル(cargo build)と実行(cargo run)

コードは、

https://isehara-3lv.sakura.ne.jp/blog/2023/01/19/rustの基本的なsyntax/

% cargo run --target x86_64-apple-darwin     

    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/x86_64-apple-darwin/debug/rust`
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

内部的にはRosetta2使っているだろうから、Intel用のバイナリでもそのまま実行できます。もちろん実行ファイルを直接呼び出して

% ./target/x86_64-apple-darwin/debug/rust 

も結果は同じ。Intel Macに転送して実行してもきちんと実行できました。

 

P.S. ラズパイ用のクロスコンパイルはエラーになる、追加の設定(Linker設定してないから)が要りそう。ネットの情報は古くてあまり参考になるものは少ない。

% cargo build --target thumbv6m-none-eabi    
   Compiling rust v0.1.0 (/Users/usamiryuuichi/rust)
error[E0463]: can't find crate for `std`

~~以下省略~~

 

admin

ラズパイでRust

を今年の後半はRustの勉強兼ねて使ってみようと思う。GPIOなどの固有ハードウェア操作用の基本的なライブラリはすでに存在するだろうから。

https://misoji-engineer.com/archives/rust-led.html

クロス開発環境はDocker(GolangのネーティブC環境の必要性と同じく)になるのかそれともGolangと同じようにターゲット指定で済むのか、その前にRustの基本的な部分の習得が先ですが。

https://isehara-3lv.sakura.ne.jp/blog/category/rust/

はPlayground使って初めてのRustですが、オンラインの学習用には、

https://doc.rust-jp.rs/book-ja/title-page.html

あたりが適切のように思う。

 

P.S. (2023/8/2)

クロスコンパイルはcrossツールを使いますが、実はDockerの中にターゲットのイメージを展開して実行されます。従って賢いやり方ですね、Golangと比べても。

 

admin