🌟

Rustの基本

2024/03/18に公開
3

この記事は、自分(C言語開発経験者)がRustを使って実際にプログラムを作った際に感じた、
Rustでプログラム開発する上で知っておいた方が良いこと(主にC言語と違うところ)を
記載しています。
なお、所有権やミュータブル、参照などRustの一般的な文法については、解説書をご覧ください。

  1. 変数の型指定
    変数の型宣言は、C言語では

    int a = 0;
    

    と型名が前にきますが、Rustでは

    let a : u32 = 0;
    let b : u32;
    

    のように、コロンの後ろに型名を指定します。なお初期値は指定しなくても構いません。

  2. 型エイリアス

    既存の型に別の名前をつけたい場合は、type文を使用します。例として

    type ID = usize;
    

    のように、既存の型であるusize型にIDという別名をつけることができ

    let b : ID = 1;
    

    と書くことができます。

  3. 複数のモジュール定義と呼び出し

    Rustで開発を開始する時には cargo new --bin コマンドで初期ファイルを生成しますが
    その時に main.rs というファイルが自動的に作られ、これがソースファイルの中心になります。
    一方、ある程度の規模のプログラムを作成しようとすると、機能毎にソースファイルを分けたい
    要求が出てきます。
    そうした場合、複数のソースファイルに分散した構造体やメソッドを相互にアクセス出来る
    必要があります。
    その場合には、
    ・pathアトリビュートを定義し、参照したいソースファイル名を指定する
    ・pub mod文により、参照したいソースファイルのモジュールを定義する
    ことで実現します。例は

    #[path = "config.rs"]
    pub mod config;
    

    となります。
    なおpathアトリビュートは、宣言するモジュール名とソースファイル名が同じ場合は指定は不要
    ですが、そうではない場合や別のディレクトリにある場合は指定が必要になります。
    別ディレクトリの場合の例を以下に示します。

    #[path = "boot/boot2.rs"]
    pub mod boot2;
    

    逆にあるソースファイルが、別のソースファイルに含まれる変数やメソッドを参照する場合は、

    use crate::config::{アイテム名};
    

    と記述することで、対象アイテムを名前修飾なしで利用できます。
    なお、アイテム名はコンマで区切って複数列挙したり、全てのアイテムを表す * を指定する
    事ができます。

  4. 構造体の定義と初期化

    Rustでは、構造体の宣言は

    pub struct TCB {
        pub pre: *mut TCB,
        ...
        pub waisem: isize,
    }
    

    とC言語と同じように記述できます。
    ただし、個々のフィールド毎に外部に公開するかどうかをpubで指定する必要があります。
    また、構造体を初期化するには

    TCB { pre: core::ptr::null_mut(), ..., waisem: 0 }
    

    のように配列の各メンバーの初期値を列挙する必要があります。

    更に、構造体の配列を作成し値を初期化する場合は

    static TCB_TBL: [ TCB; CNF_MAX_TSK_ID ] =
      [  TCB { pre: core::ptr::null_mut(), ..., waisem: 0 },
         ...
      ];
    

    のように初期化値を配列の要素数分だけ列挙します。

  5. Structとimplブロック
    RustにはJavaのようなクラスがなく、継承もありません。
    また、new()のような決まった名前のコンストラクタもありません。
    代わりにStructにて構造体を定義し、構造体に関するメソッドをimplブロック
    内に記述する形で実装します。

    pub struct TCBQueue {
        pub head: *mut TCB,
    }
    impl TCBQueue {
        pub fn iter(self: &self) -> TCBIterator {
            TCBQueueIterator { cur: self.head }
        }
        ...
    }
    
  6. イテレータの実装

    Rustでイテレータを実装するためのインターフェースとして、Iteratorトレイトがあります。
    Iteratorトレイトを実装することで、イテレータを作ることができます。
    なおイテレータは、前回のnext()メソッドを呼び出した時の状態を格納するための
    変数が必要になります。その変数の初期化は、イテレータオブジェクトの生成メソッド
    実行時に行います。

    pub struct TCBQueueIterator {
        pub cur: *mut TCB,
    }
    impl Iterator for TCBQueueIterator {
        type Item = *mut TCB;  // Itemにnext()が返す値の型をセット
            fn next(&mut self) -> Option<Self::Item> {
        ...
    }
    
  7. ポインタ
    Rustでもポインタを扱うことができます。ただしC言語と異なりconst,mutの
    指定が必要になります。

    cur: *const TCB
    cur: *mut TCB
    

    *const TCB とした場合ポインタの指す先の値を参照は可能ですが、変更はできません。
    *mut TCB とした場合、ポインタの指す先の値を変更することができます。
    リンクリストのようにポインタ値の更新が発生する場合は、*mut 指定が必要になります。

  8. 関数ポインタ

    C言語では、関数ポインタp_funcは

    (int *pfunc)(int,int)
    

    のように記載します。p_funcには

    pfunc = max;
    

    のように関数を代入できます。
    一方、Rustでは関数ポインタ型があり

    fn initsk() {
      ...
    }
    fn main() {
       let faddr = initsk;
       ...
       
    }
    

    として、faddrに関数ポインタ値を代入できます。

  9. null
    Rustには、NULL値の定義がありません。
    しかしアセンブラとの関係でNULLを定義したい時があるので、その際は標準ライブラリ関数

    let tcb: *const TCB = core::ptr::null();
    

    もしくは

    let tcb: *mut TCB = core::ptr::null_mut();
    

    を使う事でNULLをセットできます。なお、上記の値を参照したところ
    0x0000_0000 となっていたので、C言語のNULLと同じ扱いであるようです。

  10. 名前の付け方ルール
    Rustでは、コンパイル時文法チェックだけでなく関数や変数・定数の名前付けにも
    ルールがあり、そのルールに沿ったチェックが行われチェックに引っかかると警告が
    出力されます。そのため最初からルールに合わせたコーディングをしておく事をお勧めします。
    例)

    ・関数名:全て小文字
    ・スタティック変数、定数:全て大文字
    ・宣言されているが参照されない変数:_をプレフィックスとしてつける

  11. unsafe記述の対象

    Rustでは、メモリーアクセスコードのチェックが厳しく、以下のコーディング部分は
    unsafe {} を付けないとコンパイルが通らないようになっています。

    ・static mut変数へのアクセス
    ・ポインタのデリファレンス

    ただし、unsafeブロック内部で宣言された変数はunsafeブロックを抜けると破棄されて
    しまうため、unsafeブロックの指定範囲は処理ロジックを見て決める必要があります。

Discussion

kanaruskanarus

全体的に文化・バックグラウンドの違いをすごく感じました。僕は Rust で本格的なプログラミングに入門した人間なので、( 皮肉でもなんでもなく ) 新鮮な感覚になりました。

その上で、郷に入っては郷に従えというか、できればもう少し Rust の文化を尊重してほしいなーと思い、気になったところをざっと挙げてみました。

とはいえ Rust に興味を持ってくれたことは素直に嬉しいですし、決して責めているつもりでもないので、あまり深刻に受け取りすぎず、これからも Rust に関わり続けていただけると幸いです… !


カスタム型定義

変数の型に自分で名前をつけたい場合は

type ID = usize;

のように、type命令を使って

type を命令とは普通言わないです。

また、type 〜 で定義されるのは型エイリアスですが、「カスタム型」というと新たな型を定義しているような誤解を招くと思います。


main.rsの中で

#[path = "config.rs"]
pub mod config;

のようにパスを指定して他のソースファイルの中身をmain.rsに取り込む事ができます。

( 中略 )

なお、path命令はmain.rsから見た相対パスも記載できるので

attribute を命令とは普通言わないです。

また、module を宣言できるのが main.rs だけであるように読めますが、そんなことはないです。

module を宣言するのに path attribute が必須であるかのように読めますが、そんなこともないです。もちろん path attribute を使うかどうかは個人の好みではありますが、誤解を招かないよう、その点明記すべきかと思います。

ref: https://doc.rust-lang.org/reference/items/modules.html


逆にあるソースファイルが、別のソースファイルに含まれる変数やサブルーチンを利用する場合は、

use crate::config::*;

と記述することで、名前修飾なしで利用できます。

* で public な item を全てスコープに導入する必要があるように読めますが、実際には各 item を個別に use できますので、ここも誤解を招く表現かと思います。

また、Rust では「サブルーチン」という言葉は普通使われないと思います。


ただし、個々のメンバー変数毎に外部に公開するかどうかをpubで指定する必要があるのと、宣言の後ろのコンマは全メンバーに記載する必要がある

メンバー変数ではないです。field です。

また、コンマは最後の field にはあってもなくても構いません。

ref: https://doc.rust-lang.org/reference/items/structs.html


Structを使って構造体を定義し、構造体に関するメソッドをimpl命令で
追加するような形でオブジェクトを実装します。

impl を命令とは普通言わないです。

( また、Rust では「オブジェクト」という言葉はあまり使われない気がします )


fn next(&mut Self) -> 〜

typo ( &mut self )


Rustでも、ポインタや参照を使うことができます。ただしC言語と異なりconst,mutの
指定が必要になります。

Rust において単に「参照」というと

  • & ( immutable / shared reference )
  • &mut ( mutable / exclusive reference )

を指しますので、ここは「Rustでも、ポインタを使うことができます。」と書くべきだと思います。


let tcb *const TCB = 〜
let tcb *mut TCB = 〜

typo ( : 抜け )


アセンブラとの絡みでNULLを定義したい時があるので、その際はビルトイン関数の

core::ptr::{null, null_mut} は、その path からわかるように core module に定義されている関数であり、ビルトイン関数ではなく標準ライブラリで提供されている関数です。

def: https://github.com/rust-lang/rust/blob/80e5694d6fd38bfa7900c0fd8971b2e7bebf66fa/library/core/src/ptr/mod.rs#L523-L573

標準ライブラリはコンパイラとセットで配布される特殊なライブラリであり、それを指して「ビルトイン」というのも分からなくはないのですが、少なくとも Rust では標準ライブラリが提供する関数をビルトイン関数とは普通言わないと思います。


そのためunsafeブロックで囲むステートメントの範囲をどこまでにするかは〜

unsafe ブロック はその名の通りブロックなので、statement だけを囲む必要はないです。

ref: https://doc.rust-lang.org/reference/expressions/block-expr.html

小林 倫洋小林 倫洋

kanarus 様

mkobaです。
早速ご意見いただきありがとうございます。
当方Rustを使うのは初めてであることと、独学ゆえ言語に関する
正しい理解をできていない部分があったと思いますので、ご指摘の内容は見直したいと思います。

Rustは、複雑ではありますがメモリーセーフなコーディングが可能な言語であり
内容をきちんと理解して身につけたいと思っています。