2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust勉強中 - その14 -> ライブラリとドキュメント

Last updated at Posted at 2019-10-13

自己紹介

出田 守と申します。
しがない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
    ...
}

例えば、以下のように書きます。

main.rs
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を記述していましたが、外部ファイルにモジュールを移動させます。

outside_sum.rs
pub fn pub_sum(a: i32, b: i32) -> i32 {
    a+b
}

fn plv_sum(a: i32, b:i32) -> i32 {
    a+b
}
main.rs
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をライブラリにする場合、

  1. main.rsをlib.rsに変更
  2. main関数をsrc/bin/bin_name.rsに移動
  3. lib.rsにアイテムを列挙

コンパイラはsrc/lib.rsがあると、そのプロジェクトをライブラリとしてビルドします。

新規でライブラリを作成する場合は、

$ cargo new --lib lib_name

こうすることで[lib_name]のライブラリを作成できます。

ライブラリ内の実行ファイル

src/binディレクトリに実行ファイル.rsを置くと、実行ファイルがそのライブラリをインポートし実行することができます。

library/src/lib.rs
pub fn sum(a: i32, b: i32) -> i32 {
    a+b
}
fn sub(a: i32, b: i32) -> i32 {
    a-b
}
library/src/bin/sum.rs
extern crate library;
use library::sum;

fn main() {
    println!("sum(1, 1) = {}", sum(1, 1));
}
library/src/bin/sub.rs
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を以下のように追記します。

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ファイルとして書いていきます。

src/lib.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
}
tests/test.rs
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つで認識されます。

lib.rs
//! 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

ソース

module/src/outside_sub_calc/mod.rs
pub mod sub;
pub mod mul;
pub mod div;
module/src/outside_sub_calc/div.rs
pub fn div(a: f64, b:f64) -> Option<f64> {
    if b==0.0 {
        None
    } else {
        Some(a/b)
    }
}
module/src/outside_sub_calc/mul.rs
pub fn mul(a: i32, b: i32) -> i32 {
    a*b
}
module/src/outside_sub_calc/sub.rs
pub fn pub_sum(a: i32, b: i32) -> i32 {
    a+b
}

fn plv_sum(a: i32, b:i32) -> i32 {
    a+b
}
module/src/outside_sum.rs
pub fn pub_sum(a: i32, b: i32) -> i32 {
    a+b
}

fn plv_sum(a: i32, b:i32) -> i32 {
    a+b
}
module/src/main.rs
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
}
tests/src/lib.rs
//! 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
}

今回はここまでー。
ついに、ファイルを分割できるようになりました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?