プログラミング言語Rust基礎#1

どうも,空気になりたい凡人です。
本来はno_stdで作りたいものがあるのですが,
基本を知らないのはまずいということでstdを含めた一般Rustの学習記録を書いていきます.
rustのバージョン等は次のとおりです.

  • rustc:1.93.1(2026-02-11)
  • cargo:1.93.1(2026-02-11)

Rustとは?

そもそもRustプログラミング言語は何者か,よく知らなかったので調べました.
Firefoxを提供しているMozillaが開発支援している,オープンソースとなっています.
対応可能な範囲からC言語と同じ低レイヤー言語です.
[低レイヤーとはCPUやメモリ等のハードウェア,C言語などはここに直接指示出し可能]
no_stdの方でもasm!でアセンブリを使用してCPUに指示出しをしていましたね.

そして低レイヤー言語の宿命ですが,
作成したプログラムのハードウェアで実行する内容や保管場所などを手動で指示出しする必要があります.
pythonとかではプログラム内で変数を定義することによるメモリ消費など考えませんが,C言語やRust等は注意が必要です.

Rustの特徴は以下が挙げられます.

  • 処理速度はCより少し遅くC++並
  • 並列処理が可能
  • 高いメモリ安全性
  • 高いパフォーマンスで安定動作可能

Rustでよく聞くワードとしては以下が挙げられます.

  • メモリ安全性
  • スタック/ヒープ
  • ガベージコレクション(GC)
  • 所有権
  • 借用チェッカー

特に所有権と借用チェッカーは,Rust学習の壁と言われるほど重要かつ厄介なものです.
それぞれは単一でいずれ取り上げます.

Cargoとは?

次に通常のrustupによるプログラムファイル作成ではなく,Cargoを取り上げます.
Cargoは作成したプログラム間の依存関係を管理する機能があります.
例えばプログラミングではパッケージやライブラリを呼び出して利用するケースが多いです.
Rustでは使用するライブラリを「依存」と呼びCargoでは手動で予め設定できます.
具体的にはCargo.tomlで大枠を管理します.

Cargoの作成は次のようになります.

cargo new learn_1

これでleran_1というディレクトリが作成されます.
構造は次のとおりです.

Cargo.toml
/learn_1
|--Cargo.toml
|--.git
|  |--*** (複数あるため省略)
|--src
|  |--main.rc

プログラムの内容はmain.rcに記述します.
またデフォルトでは"Hello World!"を出力するようになっています.
実行はlearn_1ディレクトリ内で次の3種類いずれかで実行できます.

cargo run ## 検証やテスト実行(頻繁に使用)
cargo build ## 実行には./target/debug/learn_1が追加で必要
cargo check ##バイナリを生成せずに実行しエラー検証を行う

またCargo.toml内部は以下のようになっています.

[package]
name = "learn_1"
version = "0.1.0"
edition = "2024"

[dependencies]

初期は依存関係など特に定義されていません.
また依存関係だけでなく,先程の実行コマンド3種類の実行ルールも決定できます.


今回はここまで!

参考文献

doc.rust-jp.rs

no_stdで行くRust #3.1

どうも,空気になりたい凡人です。
#3では"Hello World!"の標準出力経由での表示と出力・終了コードを関数化できました.
本当は標準入力を実装し,標準出力で表示...をしたかったのですが,うまく行かなかったとだけ.

なぜうまく行かなかった?

まず標準入力のインラインアセンブリは次のように記載できます.(書き方は色々ある)

...
fn sys_read(fd: u32, buffer: &mut [u8]) -> usize {
    let ret: usize;
    unsafe {
        asm!(
            "syscall",
            in("rax") 0, // sys_read
            in("rdi") fd,
            in("rsi") buffer.as_ptr(),
            in("rdx") buffer.len(),
            lateout("rax") ret,
            options(nostack, preserves_flags),
        );
    }
    ret
}
...

標準入力のシステムコールは0であり,出力では固定変数から取っていたポインタや単語のバイト長を関数の変数から取ってきています.
これで動作自体は出来るはずなのですが,例のごとくccリンカエラーを履きます.
それも以前は回避したはずの全く同じエラー文です.

ここで以下を試していきました.

  • _start内に直接標準入力のインラインアセンブリを書く
  • 標準出力を消して純粋に標準入力だけ記述する
  • 変数の型を可変から固定長にする(mut[u8]で可変文字列に対応できるようにしていた)

が,どれもうまく行かず,同じエラーのまま.
色々調べたりいろんなLLMモデル二聞くなどで原因の整理をしようとしましたが,
どれも解決どころか混乱するだけでした.

今後どうする?

ccリンカエラーとメモリ不整合などno_std及びRustでよく聞く問題も踏んだので一度no_stdでできること,
実行する際に考慮することを整理することにします.
短めですが,すぐに挙げます.

no_stdで行くRust #3

どうも,空気になりたい凡人です。
#2では,#1で現れたエラー文たちを読み解き,
インラインアセンブリによるno_std環境で正常終了できるまで設計しました.
今回は,プログラミングでおなじみの"Hello World!"の出力をしていきます.

1.まずは標準出力

早速アセンブリの部分に加筆していきます.

#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {

    let msg = "Hello World!\n";
    let ptr = msg.as_ptr();
    let len = msg.len();
    
    unsafe{
        //標準出力
        asm!(
            "syscall",
            in("rax") 1,
            in("rdi") 1,
            in("rsi") ptr,
            in("rdx") len,
            out("rcx") _,
            out("r11") _,
        );

        asm!(
            "syscall",
            in("rax") 60,
            in("rdi") 0,
            options(noreturn)
        );
    }  
}

まず基本のRustの変数です.
"let"文を使用して定義でき,今回の方法は不変値となっています.
途中で値を変更できる可変変数にする場合は"let mut"で定義できます.

次にletで定義している内容ですが...

  • msg = "Hello World!\n":ただの文字列(std環境ならこれで出力まで出来る)
  • ptr = msg.as_ptr():文字列(msg)我格納されたポインタ(データ置き場)を取得する
    • .as_ptr():変数に対するポインタを取得するオプション
  • len = msg.len():文字列の長さ(Byte数)を取得する
    • .len():変数に対するByte数を取得するオプション

次にusafe内に追記した箇所です.
今回はLinuxカーネル内の"Write"システムコールを使用するため,
raxに1を渡します.
次に前回は終了コードだったrdiを1の標準出力に指定します.
これはrdiが渡す情報がシステムコールごとに違うためであり,
writeの場合は「どの形式で出力するか」を格納します.

他レジスタは次の通りです.

  • rsi:システムコールで転送する文字列のポインタを格納(Register of Source IndexでRSIと読めばいいかも)
  • rdx:文字列のByte長を格納(Register of data,raxで使用するデータを格納する)
  • rcx:一時データ保管場所その1(正確にはloop処理でCPUが利用できるデータの保管場所)
  • r11:一時データ保管場所その2(64bitでのデータ格納レジスタ)

つまりin(***)でやっていることは,

  1. Writeシステムコール呼び出し
  2. 出力指定
  3. 出力するデータ元参照
  4. データのByte数呼び出し
  5. 実際に出力

までを行っています.

out(***)が2箇所ついていますが,別になくても出力できてしまいます.(実験済み)
これを実行する理由は,スクリプト実行で格納した一時データのレジスタを破壊してCPUに使用させないためです.
これをしない場合,ループ用データの格納場所にどんどんCPUが上書きし計算処理などをさせた際に誤った出力をする原因になります.

ちなみに放置すると,次のような異常を引き起こす可能性があります.

  1. コンパイラがデータをrcxに書き込み
  2. asm!部分でCPUがrcxを上書き
  3. システムコール終了後に,コンパイラがrcxにデータがあると誤認
  4. 誤認したデータをもとに計算を続けてしまう(最悪出力がバグる)

詳細はABI(Application Binary Interface)の仕様を参照してね.(バイナリの知識はさすがにここでは追わない)

さて,追記したファイルを実行してみます.

   Compiling no_std v0.1.0 (/*********)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.66s
     Running `t********`
Hello World!

やったぜ!

2.アセンブリをできるだけ隠したい

さて表示するだけなら,アセンブリを書きまくればできます.
しかしそんなコード,今後システムを組んだり保守運用する場合に見たいか?
というわけで関数として設計し,今後保守箇所を集中出来るようにします.

# ![no_main] 
# ![no_std]

use core::panic::PanicInfo;
use core::arch::asm;
 
// パニック時の処理   
# [panic_handler]
fn panic(_info: &PanicInfo) -> !{
    loop {}
}

//終了コード呼び出し関数
pub fn sys_exit(status: i32) -> ! {
    unsafe{
        asm!(
            "syscall",
            in("rax") 60,
            in("rdi") status, //終了コード指定
            options(noreturn)
        );
    }
}

//標準出力関数
#[cfg(target_arch = "x86_64")]
fn sys_write(fd: i32, data: &[u8]){
    let ptr = data.as_ptr();
    let len = data.len();

    unsafe{
        //標準出力
        asm!(
            "syscall",
            in("rax") 1,
            in("rdi") fd,
            in("rsi") ptr,
            in("rdx") len,
            out("rcx") _,
            out("r11") _,
        );
    }
}

//文字列出力関数
pub fn print(s: &str){
    sys_write(1,s.as_bytes());
}

//システム終了パート
#[unsafe(no_mangle)]
pub extern "C" fn _start() ->!{
    //正常終了
    sys_exit(0);
}


まず終了部分で,終了のシステムコールを独立して関数化しました.
ただしここでは実行はされません.
また終了コードを引数化したため,
正常終了以外の終了コード発行が可能になっています.
(panic_handlerのloop{}処理も実はこれで整理できたり...)

次にwriteのシステムコール部分を"sys_write"関数として定義しました.
変数として"fd","data"を定義しており,次の型を指定しています.

  • i32:符号付き32bit整数を指定する型
  • &[u8]:符号なし8bit整数(u8)を用いたバイト文字列を指定する型
    • 表記「b"Hello ***"」と先頭に"b"を付与

またこの関数の先頭に「#[cfg(target_arch = "x86_64")]」を付与しています.
これはRustでのコンパイル対象がx86_64アーキテクチャの場合に実行する指示になります.
IF文だけでなくこのような方式でも,分岐が作成できるのは覚えておきたいですね.

print関数では変数"s"に対し,{}内の実行を行います.
作成したsys_write関数をここで実行しています.
std環境ならこの段階で,main関数で引数定義で実行すれば文字出力できるんですけどね...

最後に,途中でエラーが発生しなかった場合にsys_exit(0)で正常終了できます.

これでunsafe内で複数同時実行ではなく,役割で分離,整理ができました.

今回で以下の項目を達成しました.

  • 実際の文字出力
  • 出力と終了に関するインラインアセンブリの関数・モジュール化

次回は関数経由での文字出力と入力モジュール化を行い,入力⇒出力の仕組みを作ります.

SQL初めました #1

どうも,空気になりたい凡人です。
SQLの学習日記として,ここに投下していきまーす.
(また本業に関係なしの学習...)

1.SQLて何さ?

SQLはDBMSを操作するための言語,
というよりほぼ実行命令だけなので他言語ほどプログラミングしていないと感じます.

  • DB(データベース):構造化した情報またはデータの集合体
  • DBMS(データベース管理システム)」DBを管理・制御する仕組み
  • SQL(Structured Query Language):DBMSを管理する言語

Queryがもろに「要求」や「質問」とあるように,SQLは事実上1つの主張しかなかったりします.
データの入れ物を作る・入れ物からデータを呼び出す・データを編集する...
これらはすべて一つの命令として実行する必要があります.
プログラミングでよくある「,」や「;」による区切りは最後の一度しか登場しません.
(初期はこれで何度かエラーしました)

なぜデータや情報をDBで管理する必要があるかなどは,
DBエンジニアではないので明確ではないですが
利用ケースと調べた限りでは次のようです.

  • Excelなどの表計算ソフト:単一ユーザーが作成・編集する(基本的に同時編集は例外)
  • DB:複数のユーザーが同時に作成・編集する(Webサイトのユーザーデータなど同時に更新があり得るもの)

DBも閲覧するときはExcelと同様のセル形式に見えますが,
同時アクセス・編集を前提にしています.
特にExcelのシートはExcelではないと閲覧できませんが,
DBならばSQLの送信でデータの呼び出し・閲覧・利用が可能になります.
SQLを送信する機能さえあればOKな点も,閲覧性で表計算ソフトより優れています.
(データが多くなるとExcelの展開だけで簡単にフリーズするしね...)

当然,少数データの表示・閲覧ならExcelなど表計算ソフトが圧倒的に便利です.
あくまで大量のデータを,幅広いユーザー・端末より編集・閲覧が行える点でDBが採用されるというイメージです.

今回は以下の環境でやっていきます.

  • OS:Ubuntu 24.04.3 LTS
  • SQL:sqlite3 3.51.2

Excelと対応する用語

  • DB:Excelのファイル
  • テーブル:Excelのシート
  • レコード:Excelの行
  • カラム:Excelの列

2.SQLでデータベースを制御する

DBの作成

まずは,データベースファイルを作成します.
次のコマンドをDB作成ディレクトリで実行します.

# sqlite3 test.db
sqlite>

これで,sqliteが起動した状態になり,テーブルが作れるようになります.
テーブルはExcelのシート部分に相当します.
なのでテーブルを複数作って,データのやり取りが可能になります.

テーブルの作成

次のコマンドでテーブルが作成されます.

sqlite> CREATE TABLE test_table (
(x1...> id INTEGER PRIMARY KEY,
(x1...> name TEXT,
(x1...> property TEXT
(x1...> );

このコマンドにより,id|name|property|の3つの列を持つテーブルが作成されました.
()内の記載は次の順です.

  • ラベル名 型 制約,

ラベル名はそのままで,型は列に記載される文字の型を指定しています.
例えば整数値の場合は'INTEGER',文字列なら'TEXT'です.(STRINGではない...)
最後の制約はわかりにくいですが,次の2つはテーブルの管理には有用だと思います.

  • PRIMARY KEY:この列

次にレコードを入力します.

sqlite> INSERT INTO test_table VALUES(1,'admin', 'Administrator');

これでレコードが登録されたので確認してみましょう.

sqlite> .mode column
sqlite> .header on
sqlite> SELECT * FROM test_table;
id  name   property     
--  -----  -------------
1   admin  Administrator

カラムを見やすくするために,カラム単位で空白がつく「.mode column」,カラム名を表示する「.header on」を先に実行しています.
SELECTの後ろは表示する「カラム名」,FROMの後ろが表示する「テーブル名」,「;」で命令を終了します.
すべてのカラムを取得する場合はワイルドカード「*」で十分です.

ルール違反すると?

では早速制約違反をしてみます.

sqlite> INSERT INTO test_table VALUES(1,'admin2','Administrator2');
Runtime error: UNIQUE constraint failed: test_table.id (19)

このように,idが一意じゃないのでダメと怒られます.
命令ごとに実行前に怒ってくれるあたり,Rustみを感じる.
本物のUnix系なら保護なしでそのままデータ挿入・そのまま崩壊とかあり得るし...

次はデータ型のルールを破るとどうなるのでしょう.

sqlite> INSERT INTO test_table VALUES('ad','admin2','Administrator2');
Runtime error: datatype mismatch (20)

はい,型に関しても違反できないようになっています.
あくまでPRIMARY KEY指定したカラムは.

sqlite> INSERT INTO test_table VALUES(2,'admin2',2);
sqlite> SELECT * FROM test_table;
id  name    property     
--  ------  -------------
1   admin   Administrator
2   admin2  2

なんと制約なしの場合は,型違反したデータをカラムに登録できてしまいます.
例では新規で追加したレコードのpropertyカラムにINTEGER型の2を設定しましたが,
エラーなく通ってしまいます.
ただしこれはSQLiteの仕様で,内部的にはINTEGER型に変換して格納されるらしいです.
一般的なMySQLやPostgresqlなどは,PRIMARY KEY同様に型を守らなければいけません.
またSQLiteでも,CRATE文の最後にSTRICTをつけることで型違反を禁止できます.

レコードの削除

では型違反しているレコードを削除してみます.

sqlite> DELETE FROM test_table WHERE id =2;
sqlite> SELECT * FROM test_table;
id  name   property     
--  -----  -------------
1   admin  Administrator

正常に削除できました.
WHERE文は,カラムの条件指定を行っており,DELETE以外の他命令でも使用されます.


では最後にSQLiteを終了しておきます.

sqlite> .exit
# 

これで正常終了できました.

以上でデータの作成削除操作を網羅したので,
次回はレコードの更新とソートなどのSELECT文による表示を整理します.
(まぁ,書くだけなら簡単なんですが)

no_stdで行くRust #2

どうも,空気になりたい凡人です。
#1では,no_std環境で一応エラーなくコンパイル完了するまで実施しました.
その中で複数のエラー文を解決したため,
今回はそのエラー内容を読み解いていきます.

1.unwinding panics

この"unwinding"とは,「解く」「巻き戻す」といった意味があります.
デフォルトでは,stdでのpanic_handlerと同様に自動で「処理の巻き戻し」が行われます.

エラーが起こるまでに読み込んだ関数のデータを捨てる処理ですね.
この処理自体は,読み込んでいたデータの掃除処理が走ります.

「無駄が多い」かつ「処理そのものが複雑になる」点から,
最小構成のno_std環境とは思想が違うわけですね.(そもそも道具が足らない)
では設定していた「panic = abort」ですが,abortを指定することで「巻き戻し」を行わず,「異常終了処理」を行うようになります.
これによって,main.rc内で定義していたpanic_handlerの処理を実行してくれるようになるわけです.

因みに設定していたCargo.tomlの以下の表記ですが...

```
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

.devが「cargo build」で作成する際のルール,
.releaseが「cargo release」で実行ファイルを作成する際のルールですね.

2.linking with `cc` failed: exit status: 1

次はC言語のランタイム問題です.
デフォルトではRustのコンパイラ(バイナリに変換するもの)は,
C言語のものを使用します.
os.phil-opp.com
そしてこれを突っぱねるのが,前回の記載です.

[target.x86_64-unknown-linux-gnu]
rustflags = [
    "-C", "link-arg=-nostartfiles",
    "-C", "link-arg=-nodefaultlibs",
]
  • "-nostartfiles":コンパイラやOS側が事前準備するコードをリンクさせないようにする
  • "-nodefaultlibs":cc(C言語コンパイラ)がリンクしようとするライブラリを無視させる

これでno_std環境でC言語を介在させない世界かつ,C言語を取りにいかない設定を構築できるわけですね.

そして,前回のmain.rcファイルでは_startの自作をしていませんでした.
なので今回修正し以下のようになります.

# ![no_main] 
# ![no_std]

use core::panic::PanicInfo;

// パニック時の処理   
# [panic_handler]
fn panic(_info: &PanicInfo) -> !{
    loop {}
}

// OSから直接呼ばれるエントリポイント
#[no_mangle]
pub extern "C" fn _start() -> ! {
    // ここで何もしない
    loop {}
}

「extern "C" 」で_start()関数を設定することにより,
呼び出し方はC言語と同じにする宣言になります.

そして,こちらは実行すると永遠に終わらないプログラムになります.
OSに例えると,これが起動した状態の維持を意味するわけですね.
ちなみにstd環境の場合は,終了する「exit」も勝手にやってくれるため,
プログラミング言語のようにexitを明記しなくとも終了してくれます.

逆に言えば,前回最後の終了できていたことが間違いというね.

3.せっかくなので終了するまでのセットを作ってみる

さて,インラインアセンブリを使って終了できるプログラムを作ります.
アセンブリ言語は全然知らないから間違ってることも)
no_stdである以上,正常終了ではなくシステムとして終了させる必要があります.
そのため,システムコールによる終了を設計しました.

# ![no_main] 
# ![no_std]

use core::panic::PanicInfo;
use core::arch::asm;
 
// パニック時の処理   
# [panic_handler]
fn panic(_info: &PanicInfo) -> !{
    loop {}
}

// OSから直接呼ばれるエントリポイント
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
    
    unsafe{
        asm!(
            "syscall",
            in("rax") 60,
            in("rdi") 0,
            options(noreturn)
        );
    }  
}

まずcoreライブラリ内のcore::arch::asmを使用します.
これはRustのスクリプト中にアセンブリを組み込めるマクロです.
(まさかのマクロ復活,coreライブラリなので制限なしよ)
先程loop{}でCPUに永久ジャンプさせていたところを,
システムコールで終了するように変更されています.
システムコール60番は「exit」を意味し,正常終了コードとして「0」を使用します.

そして前提としてインラインアセンブリはRustの根幹であるメモリ安全性などが自己責任になるため,「unsafe」内に設計することが求められます.
各行の意味は以下のとおりです.

  • syscall:システムコールの呼び出し宣言
  • in():括弧内のレジスタへ割り当てる設定
  • rax:システムコール番号を格納(意味的には_start関数の戻り値を格納するらしい)
  • rdi:終了コードを格納(意味的には_start関数の第一引数を格納するらしい)
  • option(noreturn):アセンブリ読み込み後に関数へ戻さないことを指定

今回_start戻り値に「!」を指定しており,絶対に終了しないことを前提としています.
そこでアセンブリ部分を読ませ最後のoption(noreturn)で_startに戻さないようにします.
これで次の行以降は実行しないことをコンパイラに伝達して_startの終端(終了)へ到達しないことになります.
これで!の型(Never型)を守ることができ,エラーなく終了できます.
当然「options(noreturn)」の行をコメントアウトすると型が一致しないと怒られます.

error[E0308]: mismatched types

また終了コードのrdiに格納する値はLinuxの$?に格納されるので,
書き換えた場合の出力が変わることを次のコマンドで確認できます.

cargo run ; echo $?

今回で以下の項目を達成しました.

  • no_std環境での正常終了させるまでの作法
  • アセンブリに関する記載

次回はプログラミング言語の"いつもの",「Hellow World!」の出力をやっていきます.

no_stdで行くRust #1

どうも,空気になりたい凡人です。
ひょんなことからno_std環境でのRustを学習する必要があるので,
記録がてら書いていきます.

no_stdて何?

まず前提であるno_stdとは何かに触れます.

Rustでスクリプトを記述する際には,
関数やマクロを初めから使用可能な状態になっています.
例えば次のような文字列を出力するマクロがあります.

    println!("Hello , World!");

出力は例のごとく「Hello , World!」です.
これは前提である標準パッケージ(std)に含まれる道具になります.
no_stdはこのような基本的なツールが一切使用できない,
とんでも環境になります.
使用用途としては,WindowsLinuxMacOSなど決まったOSに依存しない組み込み開発やOSそのものの開発ですね.

何がしたいの?

stdを捨ててまで何をするかというと,
単純に数学をRustで実装する方法論を学習したかったためです.
欲を言えばより低レイヤーの世界で,
どこまで数学論理を実装できるのか気になったからですね.
本当にそんなレベルの理由です.(本職とは関係なし...)
まぁ,世の中std環境などごまんとあるので,
no_std環境ならまぁ書いてもいいかなと思っています.
ちなみにRustは,std環境で数学ちょっとやった程度のペーペーです.(おい...)

最初の洗礼

では早速やっていきます.
私の環境は以下のとおりです.

  • OS:Ubuntu 24.04.3 LTS
  • rustc:1.92.0
  • cargo:1.92.0

では早速「cargo new」で新規cargoを作成し,以下のmain.rcファイルを作成します.

# ![no_std] //これがno_stdを宣言する

fn main() {
    println!("Hello, world!");
}

基本はstdありなので,「# ![no_std] 」を記載して明示的に宣言する必要があります.
ではこれで「cargo run」してみましょう.
すると色々怒られます.

error: cannot find macro `println` in this scope
 --> src/main.rs:5:5
  |
5 |     println!("Hello, world!");
  |     ^^^^^^^

error: `#[panic_handler]` function required, but not found

error: unwinding panics are not supported without std

エラーはそれぞれ「printlnマクロ見つからん!」「panic_handlerねぇ!」です.
マクロがないのは,導入であげた内容ですね.

では後者のPanic_handlerとは何でしょう.
これはRustのプログラム実行時に異常が起こった際の対応プログラムになります.
Linuxカーネルパニック(Windowでのブルースクリーン対応)などが,概念的に近いです.
stdありでは,これをOS依存のツールやルールで勝手に処理してくれますが,no_stdではそれを手放しているので自己責任になります.

今回はこのpanic_handlerを解消しコンパイル完了するまでをゴールにします.

コンパイルが通るまで

まず以下のようにスクリプトを改修します.

# ![no_main]  //main関数がないことを指定
# ![no_std]     //no_stdを宣言

use core::panic::PanicInfo; //coreからpanic関数をスコープに導入


# [panic_handler]
fn panic(_info: &PanicInfo) -> !{
    loop {}  //無限ループ処理
}

fn panicの列では,以下の操作をしています.

  • _info:引数指定を無視
  • &PanicInfo:引数の型(後続に本来は引数あり)
  • -> !:Never型(fn panic関数が戻り値を返さない設定)
  • loop{}:無限にループ処理をさせる

無限にループさせることで何が起きるか,
これはCPUを同じメモリの番地にジャンプさせ続けるアセンブリ言語側の挙動を真似ています.
要は,同じ場所でずっとジャンプさせてCPUが余計な処理をしないよう「しまっちゃうよ」してるわけです.
なので異常状態の解消ではなく,あくまで通常のフリーズに安全に持っていけるだけだったりします.

これで先程のエラー文は解消しました.
で再度cargo runすると...

error: unwinding panics are not supported without std

今度はstdなしで「unwinding panics」をサポートしていないと出ています.
これの無効化は,よく資料があるCargo.tomlに以下を加筆する方式でいきます.

```

[profile.dev]
panic = "abort"

[prifile.release]
panic = "abort"

デフォルトの[dependencies]に加筆しました.
これで動くやろ...

error: linking with `cc` failed: exit status: 1
= note:  "cc" ...

まだだめらしい.今度はccとの接続に失敗とあります.
(長過ぎるエラー文だったため,先頭部分だけ取りました.)
どうもこれはRustのCargoビルドがC言語標準環境を使用用途して,Rust側と衝突しているようです.

なので,Cランタイムを無視するように「.cargo/config.toml」を作成し設定をします.

[target.x86_64-unknown-linux-gnu]
rustflags = [
    "-C", "link-arg=-nostartfiles",
    "-C", "link-arg=-nodefaultlibs",
]

(2026/02/10:修正)
この設定は次のコマンド実行することとcargo runが同義になります.

cargo rustc -- -C link-arg=-nostartfiles
cargo rustc -- -C link-arg=-nodefaultlibs

ファイル中の「target.x86_64-unknown-linux-gnu」はRustをコンパイル・ビルドする環境を指しており,次のコマンドで確認できます.

rustup target list|grep linux

というわけで,これで問題は解決されたはずなので,実行!

warning: unused manifest key: prifile
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/no_std`
Segmentation fault (コアダンプ)

何かWarning出てるけど,一応コンパイルは最後まで通ったらしい.
やったぜ!(コアダンプという文字から目を背けつつ)

次の目標

今回は飛ばし飛ばしで,コンパイルまでこぎつけましたが,
次回は道中で流した箇所(リンカやcoreなど)を深堀します.

あなたもawk職人になろう!!(初心者)

どうも,空気になりたい凡人です。

今回の技術記事は、Linuxawkに目覚めたので少しだけまとめました。

なぜ今回はawkか?

sedawkシェルスクリプトの中でも、
有名所で使いこなせればファイル操作が簡単になるそうな。

しかし、シェルの深淵らしく分かりづらいということで、
長らく食指が伸びていませんでした。

そして、最近仕事の方でとある操作をする際に簡単に実行しました。
これがまぁ楽になるんです。
正直今回取り上げる方法だけならPowershellより簡単かも?

特定の列だけ取り出す

さて、実際に使った方法はこちら

systemctl list-units --type=service --all

これは、LinuxのSystemdが管理するサービスを一覧表示するコマンドです。
例えばRsyslog(ログ収集)やSSH(遠隔接続)などを管理しています。

で、Linuxサーバの設計書を作成する場合だと、
このサービス名と実際に動作しているかの設定値を記載することになります。
ですが、出力結果は実際には以下のよう...

UNIT         LOAD        ACTIVE     SUB     DESSCRIPTION
atd.service  loaded      active     running Deferred execution scheduler
   :           :           :         :               :

成分ごとに並んでいるので、
例えばコレをTera Termなどで丸々コピーしExcelに貼り付けると、
一列のシート内に情報が入力されてしまいます。
閲覧性最悪ですし、運用する上で設定が変わるたびにセルを選択する必要が出てきます。

そこでawkです。
これを次のように使うことで、Unitの列のみ表示できます。

systemctl list-units --type=service --all |awk '{print $1}'

何をしているかというと、systemctlで出力した表から、
半角スペース含む空間区切りで1つ目(引数$1で取得)を出力しています。
当然$2や$3を取ればLOADやACTIVEの列も取得できます。
awk前のコマンドが一致していれば、
UNITとLOADは対応するので列ごとにコピーすることができます。
資料を作る小技みたいなものですね。

ちなみにawkの後ろの{}は'(シングルクォート)で囲う必要があります。
"(ダブルクォート)では[systemctl list-units --type=service --all]と同じ結果でした。
おそらく$DAYなどローカルな変数ではなく{print $1}直接をawkに渡すから...
ですかね?

ちなみに先ほどのコマンドでは、
一部例外で"・"が入ったサービスが存在します。

・tlp.service  not-found   inactive     dead   tlp.service 

これは何らかの理由でサービスが停止しているものについています。
基本的に、詳細設計書などに記載するのは以下の2点に絞られます。

  • 動作しているサービス
  • 意図的に停止させるサービス

したがって、次のようなコマンドを使えば十分になります。

systemctl list-units --type=service --all |grep -v "dead" |grep -v "failed"|awk '{print $1}'

(grep -vは指定した文字を含まないものを表示します)
これによって、activeであるサービスのみ取得できます。
あとはexcelなり資料にコピーすればOKです。
個別で停止しているサービスは、適切な場所に列挿入すればいいですし。

おわりに

漠然としたイメージだけでずっと触っていませんでしたが、
実際にやってみると簡単でした。
次扱うときは、しっかりawkの理解を深めた状態でしたいです。
今回は以上となります。ではでは