自己紹介
出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。
Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。
環境
新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.38.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell
前回
前回は式とエラーハンドリングについて学びました。
Rust勉強中 - その13
Module
moduleにはアイテムを含めます。
アイテムとは、関数や型、型エイリアス、implブロック、定数、Module、インポート、externブロックのことです。
内部ファイルにmoduleを書く際は以下のようにします。
mod module_name {
// items
...
}
例えば、以下のように書きます。
mod inside_sum {
pub fn pub_sum(a: i32, b: i32) -> i32 {
a+b
}
fn plv_sum(a: i32, b:i32) -> i32 {
a+b
}
}
fn main() {
// ./src/main.rs
println!("inside_sum::pub_sum(1, 1) = {}", inside_sum::pub_sum(1, 1)); // inside_sum::pub_sum(1, 1) = 2
// println!("inside_sum::plv_sum(1, 1) = {}", inside_sum::plv_sum(1, 1)); // Error: function is private
}
このとき、modに囲まれた関数の先頭にpubを付ければ外からアクセス可能なパブリック関数となります。もし、付けなければ外からアクセスできないプライベート関数になります。上記の場合は、pub_sum関数はパブリック関数で、plv_sum関数はプライベート関数です。
ファイルの分割
ここにきてついに、ファイルを分割する方法を学びます。
moduleごとにファイルを分割したい場合があります。
先ほどは直接main.rsのファイル中にmoduleを記述していましたが、外部ファイルにモジュールを移動させます。
pub fn pub_sum(a: i32, b: i32) -> i32 {
a+b
}
fn plv_sum(a: i32, b:i32) -> i32 {
a+b
}
mod outside_sum;
fn main() {
// ./src/main.rs
println!("inside_sum::pub_sum(1, 1) = {}", inside_sum::pub_sum(1, 1)); // inside_sum::pub_sum(1, 1) = 2
// println!("inside_sum::plv_sum(1, 1) = {}", inside_sum::plv_sum(1, 1)); // Error: function is private
// ./src/outside_sum.rs
println!("outside_sum::pub_sum(1, 1) = {}", outside_sum::pub_sum(1, 1)); // outside_sum::pub_sum(1, 1) = 2
// println!("outside_sum::plv_sum(1, 1) = {}", outside_sum::plv_sum(1, 1)); // Error: function is private
println!("::outside_sum::pub_sum(1, 1) = {}", ::outside_sum::pub_sum(1, 1)); // ::outside_sum::pub_sum(1, 1) = 2
}
moduleファイルoutside_sum.rsにはアイテムを淡々と列挙し、main.rsではそのmoduleをインポートするためにmod outside_sum;
を記述しています。main関数内の最後の行で::outside_sum::pub_sum(1, 1)
としており、先頭に::演算子を付けています。これは絶対パスを意味しており、ルートディレクトリからみてのoutside_sum::pub_sumという意味です。
複数のインポートは{}
で囲って列挙します。
サブモジュールから親モジュールのアイテムのアクセスするには、明示的にサブモジュール内で親モジュールをインポートしてあげる必要があります。
use super::path;
この時、superは現在のmoduleの上のmoduleを表します。
また、selfを使うと現在のmoduleを表します。
use self::path;
このようにして、相対パスを使うことができます。
*を使えば、パブリックアイテムを全てインポートできます。
use path::*;
preludeとは
std::preludeはよく使う型や関数を自動的にインポートします。他のpreludeは*を使って、インポートしてほしいという目印として考えておきます。
ライブラリ
複数の別のcrateで、moduleを使いたくなった場合、moduleをライブラリとして作成しほかのcrateでも使えるようにする必要があります。
もし、既存の実行ファイルcrateをライブラリにする場合、
- main.rsをlib.rsに変更
- main関数をsrc/bin/bin_name.rsに移動
- lib.rsにアイテムを列挙
コンパイラはsrc/lib.rsがあると、そのプロジェクトをライブラリとしてビルドします。
新規でライブラリを作成する場合は、
$ cargo new --lib lib_name
こうすることで[lib_name]のライブラリを作成できます。
ライブラリ内の実行ファイル
src/binディレクトリに実行ファイル.rsを置くと、実行ファイルがそのライブラリをインポートし実行することができます。
pub fn sum(a: i32, b: i32) -> i32 {
a+b
}
fn sub(a: i32, b: i32) -> i32 {
a-b
}
extern crate library;
use library::sum;
fn main() {
println!("sum(1, 1) = {}", sum(1, 1));
}
extern crate library;
use library::sub;
fn main() {
println!("sub(1, 1) = {}", sub(1, 1)); // Error: sub is private
}
ライブラリcrateでsrc/binディレクトリの実行ファイルを実行するには、cargo run --bin bin_filename
を実行します。
$ cargo run --bin sum Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target\debug\sum.exe`
sum(1, 1) = 2
$ cargo run --bin sub Compiling library v0.1.0 (C:\Users\deta\hack\rust\library)
error[E0603]: function `sub` is private
--> src\bin\sub.rs:2:14
|
2 | use library::sub;
| ^^^
error: aborting due to previous error
sub関数はプライベート関数なのでエラーを出してくれます。
別のディレクトリにあるcrateをインポートするにはCargo.tomlを以下のように追記します。
...
[dependencies]
crate_name = {path = "../crate/path"}
テスト
ユニットテスト
ユニットテストは、#[test]
属性を指定することで次のアイテムがテスト関数になります。ユニットテストは実行ファイルでもライブラリでも機能します。
pub fn sum(a: i32, b: i32) -> i32 {
a+b
}
#[test]
fn test1_sum() {
assert_eq!(sum(1, 1), 2);
}
#[test]
fn test2_sum() {
assert_eq!(sum(sum(1, 1), sum(1, 1)), 4);
}
テストを実行するにはcargo test
を実行します。
$ cargo test Compiling tests v0.1.0 (C:\Users\deta\hack\rust\tests)
Finished dev [unoptimized + debuginfo] target(s) in 1.31s
Running target\debug\deps\tests-142668c48ca884f1.exe
running 2 tests
test test1_sum ... ok
test test2_sum ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
また、cargo test test1
とするとtest1が含まれるテストのみ実行することができます。
$ cargo test test1 Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target\debug\deps\tests-142668c48ca884f1.exe
running 1 test
test test1_sum ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
もし、テストコードが大きくなってきて、インポートコードやサポート関数などが必要になってきた場合、testsモジュールとして定義します。
#[cfg(test)]
mod tests {
fn prepare(...) {
...
}
#[test]
fn test_f1(...) {
...
}
#[test]
fn test_f2(...) {
...
}
}
結合テスト
結合テストはトップディレクトリ(srcと同じディレクトリ)にtestsを作成して、そのディレクトリに.rsファイルとして書いていきます。
pub fn sum(a: i32, b: i32) -> i32 {
a+b
}
#[test]
fn test1_sum() {
assert_eq!(sum(1, 1), 2);
}
#[test]
fn test2_sum() {
assert_eq!(sum(sum(1, 1), sum(1, 1)), 4);
}
pub fn sub(a: i32, b: i32) -> i32 {
a-b
}
extern crate tests;
use tests::{sum, sub};
#[test]
fn test_sum_sub() {
let a = 1;
let b = 1;
assert_eq!(sum(sum(a, b), sub(a, b)), 2);
}
この状態で、cargo testを実行すると、単体テストと結合テスト両方実行されます。
$ cargo test Compiling tests v0.1.0 (C:\Users\deta\hack\rust\tests)
Finished dev [unoptimized + debuginfo] target(s) in 1.13s
Running target\debug\deps\tests-142668c48ca884f1.exe
running 2 tests
test test2_sum ... ok
test test1_sum ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target\debug\deps\sum-96a857838ee1e966.exe
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target\debug\deps\test-3a76ac4fc4aa1ddd.exe
running 1 test
test test_sum_sub ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
もし、結合テストのみ行いたい場合は、cargo test --test test_name
を実行します。
$ cargo test --test test Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target\debug\deps\test-3a76ac4fc4aa1ddd.exe
running 1 test
test test_sum_sub ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ドキュメント
cargo doc
コマンドで、HTMLドキュメントを作成できます。
$ cargo doc --no-deps --open
--no-depsオプションは依存するcrateのドキュメントは作成しません。--openオプションは作成したドキュメントをブラウザで開きます。
生成されたドキュメントはtarget/docに格納されます。
ドキュメントは、pubが付いたアイテムとドキュメントコメント(ドクコメント、doc comments)を見つけて生成されます。
ドキュメントコメント
ドキュメントコメントは///または、#[doc]属性、//!で書きます。
- ///はその行がドキュメントコメントになります。
- #[doc]属性に指定された文字列がドキュメントコメントになります。
- //!はcrateかmoduleに対してドキュメントコメントになります。
ドキュメントコメントはMarkdown形式に対応しています。
コードブロック
ドキュメントコメント内にコードブロックを記述することができます。さらに記述したコードブロックのコードをテストすることができます。
コードブロックは、ドキュメントコメント内でバッククォート一つまたは三つか、スペース4つで認識されます。
//! sum and sub library!
/// sum library!
///
/// return 2
///
/// assert_eq!(tests::sum(1, 1), 2);
///
/// return 3
///
/// assert_eq!(tests::sum(1, 2), 3);
///
pub fn sum(a: i32, b: i32) -> i32 {
a+b
}
#[test]
fn test1_sum() {
assert_eq!(sum(1, 1), 2);
}
#[test]
fn test2_sum() {
assert_eq!(sum(sum(1, 1), sum(1, 1)), 4);
}
/// sub library!
///
/// return 0
///
/// assert_eq!(tests::sub(1, 1), 0);
///
/// return 1
///
/// assert_eq!(tests::sub(2, 1), 1);
///
pub fn sub(a: i32, b: i32) -> i32 {
a-b
}
これらのコードブロックをテストするには例のごとくcargo test
を実行します。または、ドキュメントテストだけ行いたい場合は、cargo test --doc
でもできます。
$ cargo test --doc Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Doc-tests tests
running 4 tests
test src\lib.rs - sum (line 12) ... ok
test src\lib.rs - sub (line 29) ... ok
test src\lib.rs - sub (line 34) ... ok
test src\lib.rs - sum (line 7) ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ソース
pub mod sub;
pub mod mul;
pub mod div;
pub fn div(a: f64, b:f64) -> Option<f64> {
if b==0.0 {
None
} else {
Some(a/b)
}
}
pub fn mul(a: i32, b: i32) -> i32 {
a*b
}
pub fn pub_sum(a: i32, b: i32) -> i32 {
a+b
}
fn plv_sum(a: i32, b:i32) -> i32 {
a+b
}
pub fn pub_sum(a: i32, b: i32) -> i32 {
a+b
}
fn plv_sum(a: i32, b:i32) -> i32 {
a+b
}
mod inside_sum {
pub fn pub_sum(a: i32, b: i32) -> i32 {
a+b
}
fn plv_sum(a: i32, b:i32) -> i32 {
a+b
}
}
mod outside_sum;
mod outside_sub_calc;
use outside_sub_calc::mul;
use outside_sub_calc::div::div;
fn main() {
// ./src/main.rs
println!("inside_sum::pub_sum(1, 1) = {}", inside_sum::pub_sum(1, 1)); // inside_sum::pub_sum(1, 1) = 2
// println!("inside_sum::plv_sum(1, 1) = {}", inside_sum::plv_sum(1, 1)); // Error: function is private
// ./src/outside_sum.rs
println!("outside_sum::pub_sum(1, 1) = {}", outside_sum::pub_sum(1, 1)); // outside_sum::pub_sum(1, 1) = 2
// println!("outside_sum::plv_sum(1, 1) = {}", outside_sum::plv_sum(1, 1)); // Error: function is private
println!("::outside_sum::pub_sum(1, 1) = {}", ::outside_sum::pub_sum(1, 1)); // ::outside_sum::pub_sum(1, 1) = 2
println!("outside_sub_calc::sub::sub(1, 1) = {}", outside_sub_calc::sub::sub(1, 1)); // outside_sub_calc::sub::sub(1,1) = 0
println!("outside_sub_calc::mul(2, 2) = {}", mul::mul(2, 2)); // mul::mul(2, 2) = 4
println!("div(2.0, 2.0) = {:?}", div(2.0, 2.0)); // div(2.0, 2.0) = 1.0
}
//! sum and sub library!
/// sum library!
///
/// return 2
///
/// assert_eq!(tests::sum(1, 1), 2);
///
/// return 3
///
/// assert_eq!(tests::sum(1, 2), 3);
///
pub fn sum(a: i32, b: i32) -> i32 {
a+b
}
#[test]
fn test1_sum() {
assert_eq!(sum(1, 1), 2);
}
#[test]
fn test2_sum() {
assert_eq!(sum(sum(1, 1), sum(1, 1)), 4);
}
/// sub library!
///
/// return 0
///
/// assert_eq!(tests::sub(1, 1), 0);
///
/// return 1
///
/// assert_eq!(tests::sub(2, 1), 1);
///
pub fn sub(a: i32, b: i32) -> i32 {
a-b
}
今回はここまでー。
ついに、ファイルを分割できるようになりました。