Rustの基本
この記事は、自分(C言語開発経験者)がRustを使って実際にプログラムを作った際に感じた、
Rustでプログラム開発する上で知っておいた方が良いこと(主にC言語と違うところ)を
記載しています。
なお、所有権やミュータブル、参照などRustの一般的な文法については、解説書をご覧ください。
-
変数の型指定
変数の型宣言は、C言語ではint a = 0;
と型名が前にきますが、Rustでは
let a : u32 = 0; let b : u32;
のように、コロンの後ろに型名を指定します。なお初期値は指定しなくても構いません。
-
型エイリアス
既存の型に別の名前をつけたい場合は、type文を使用します。例として
type ID = usize;
のように、既存の型であるusize型にIDという別名をつけることができ
let b : ID = 1;
と書くことができます。
-
複数のモジュール定義と呼び出し
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::{アイテム名};
と記述することで、対象アイテムを名前修飾なしで利用できます。
なお、アイテム名はコンマで区切って複数列挙したり、全てのアイテムを表す * を指定する
事ができます。 -
構造体の定義と初期化
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 }, ... ];
のように初期化値を配列の要素数分だけ列挙します。
-
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 } } ... }
-
イテレータの実装
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> { ... }
-
ポインタ
Rustでもポインタを扱うことができます。ただしC言語と異なりconst,mutの
指定が必要になります。cur: *const TCB cur: *mut TCB
*const TCB とした場合ポインタの指す先の値を参照は可能ですが、変更はできません。
*mut TCB とした場合、ポインタの指す先の値を変更することができます。
リンクリストのようにポインタ値の更新が発生する場合は、*mut 指定が必要になります。 -
関数ポインタ
C言語では、関数ポインタp_funcは
(int *pfunc)(int,int)
のように記載します。p_funcには
pfunc = max;
のように関数を代入できます。
一方、Rustでは関数ポインタ型がありfn initsk() { ... } fn main() { let faddr = initsk; ... }
として、faddrに関数ポインタ値を代入できます。
-
null
Rustには、NULL値の定義がありません。
しかしアセンブラとの関係でNULLを定義したい時があるので、その際は標準ライブラリ関数let tcb: *const TCB = core::ptr::null();
もしくは
let tcb: *mut TCB = core::ptr::null_mut();
を使う事でNULLをセットできます。なお、上記の値を参照したところ
0x0000_0000 となっていたので、C言語のNULLと同じ扱いであるようです。 -
名前の付け方ルール
Rustでは、コンパイル時文法チェックだけでなく関数や変数・定数の名前付けにも
ルールがあり、そのルールに沿ったチェックが行われチェックに引っかかると警告が
出力されます。そのため最初からルールに合わせたコーディングをしておく事をお勧めします。
例)・関数名:全て小文字
・スタティック変数、定数:全て大文字
・宣言されているが参照されない変数:_をプレフィックスとしてつける -
unsafe記述の対象
Rustでは、メモリーアクセスコードのチェックが厳しく、以下のコーディング部分は
unsafe {} を付けないとコンパイルが通らないようになっています。・static mut変数へのアクセス
・ポインタのデリファレンスただし、unsafeブロック内部で宣言された変数はunsafeブロックを抜けると破棄されて
しまうため、unsafeブロックの指定範囲は処理ロジックを見て決める必要があります。
Discussion
全体的に文化・バックグラウンドの違いをすごく感じました。僕は Rust で本格的なプログラミングに入門した人間なので、( 皮肉でもなんでもなく ) 新鮮な感覚になりました。
その上で、郷に入っては郷に従えというか、できればもう少し Rust の文化を尊重してほしいなーと思い、気になったところをざっと挙げてみました。
とはいえ Rust に興味を持ってくれたことは素直に嬉しいですし、決して責めているつもりでもないので、あまり深刻に受け取りすぎず、これからも Rust に関わり続けていただけると幸いです… !
type を命令とは普通言わないです。
また、
type 〜
で定義されるのは型エイリアスですが、「カスタム型」というと新たな型を定義しているような誤解を招くと思います。attribute を命令とは普通言わないです。
また、module を宣言できるのが
main.rs
だけであるように読めますが、そんなことはないです。module を宣言するのに
path
attribute が必須であるかのように読めますが、そんなこともないです。もちろんpath
attribute を使うかどうかは個人の好みではありますが、誤解を招かないよう、その点明記すべきかと思います。ref: https://doc.rust-lang.org/reference/items/modules.html
*
で public な item を全てスコープに導入する必要があるように読めますが、実際には各 item を個別に use できますので、ここも誤解を招く表現かと思います。また、Rust では「サブルーチン」という言葉は普通使われないと思います。
メンバー変数ではないです。field です。
また、コンマは最後の field にはあってもなくても構いません。
ref: https://doc.rust-lang.org/reference/items/structs.html
impl を命令とは普通言わないです。
( また、Rust では「オブジェクト」という言葉はあまり使われない気がします )
typo (
&mut self
)Rust において単に「参照」というと
&
( immutable / shared reference )&mut
( mutable / exclusive reference )を指しますので、ここは「Rustでも、ポインタを使うことができます。」と書くべきだと思います。
typo (
:
抜け )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 ブロック はその名の通りブロックなので、statement だけを囲む必要はないです。
ref: https://doc.rust-lang.org/reference/expressions/block-expr.html
kanarus 様
mkobaです。
早速ご意見いただきありがとうございます。
当方Rustを使うのは初めてであることと、独学ゆえ言語に関する
正しい理解をできていない部分があったと思いますので、ご指摘の内容は見直したいと思います。
Rustは、複雑ではありますがメモリーセーフなコーディングが可能な言語であり
内容をきちんと理解して身につけたいと思っています。
記事の内容を更新しています