「TDDBCの課題をRustで実施し、言語仕様を学んでいこう!」というシリーズの記事です。
こちらの記事読了が前提の内容となっております。
Rustを0から学んでみた〜Part.1〜 struct, impl, テストコードの基礎編(TDD Boot Camp 課題1「閉区間」)
[ひとことで言うとこんな記事]
以下2点を学ぶ事ができます
[こんなひとにおすすめ]
[目次]
今回の課題2を見てすぐに気がつくことは、 課題1と問題文が全部一緒 ってことですかね。違うのは「閉区間か開区間か」ってところです。
見ての通り、メンバ変数と振る舞いがまったく同じ種類用意することとなるので、共通定義しておいたほうが楽だろうと思った次第です。
こういうときに利用できるものとして、RustではTraitがございます。
pub trait Range {
fn new(lower: i8, upper: i8) -> Self;
fn to_string(&self) -> String;
fn contains(&self, number: i8) -> bool;
fn equals(&self, range: Self) -> bool;
fn is_connected_to(&self, range: Self) -> bool;
}
共通の振る舞いを上記のように定義しました。
Self
と記述している部分は、new()
やequals()
等の関数は閉区間は閉区間、開区間は開区間を返却するのでそれを示したものとなります。
閉区間
// メンバ変数をtraitに定義することはできないそうです
#[derive(Clone)]
pub struct ClosedRange {
pub lower: i8,
pub upper: i8,
}
impl Range for ClosedRange {
fn new(lower: i8, upper: i8) -> ClosedRange { // Selfを返却
/**
* 〜実際の実装〜
*/
}
}
開区間
// メンバ変数をtraitに定義することはできないそうです
#[derive(Clone)]
pub struct OpenRange {
pub lower: i8,
pub upper: i8,
}
impl Range for OpenRange {
fn new(lower: i8, upper: i8) -> OpenRange { // Selfを返却
/**
* 〜実際の実装〜
*/
}
}
上記のように閉区間、開区間共に実装(impl)を準備してあげればOKです。
もちろん 未実装の振る舞いがある場合にはコンパイルで怒られますので、安心 ですね。
use tddbc_rust_practice::range::closed_range::ClosedRange;
use tddbc_rust_practice::range::open_range::OpenRange;
use tddbc_rust_practice::range::Range; // Trait自体もuseしてあげないとfnが見つからない
fn main (){
let sample_closed_range = ClosedRange::new(1, 5);
let sample_open_range = OpenRange::new(1, 5);
}
Traitとして振る舞いを定義したもの自体もuseしてあげないと、コンパイル時に振る舞いが見つからずにエラー となります。
OpenRange,ClosedRangeを実装した初期のlib.rsが以下のようなものになっています。
GitHub - h0ng0yut0 tddbc-rust-practice/lib.rs
93行。ちょっと煩雑な気もしてきました。
上記モデルを踏まえて、親 Range
、子 ClosedRange
OpenRange
の形でモジュール管理できればいいかなと考えました。
src
配下のディレクトリ構成です。
.
├── lib.rs
├── main.rs
├── range
│ ├── closed_range.rs
│ └── open_range.rs
└── range.rs
lib.rs
(module準備の大本となるファイル)では、range
を呼び出すだけ
pub mod range;
range.rs
には、共通の振る舞いを定義したtraitと、ClosedRangeとOpenRange の実装ファイルを呼び出しています。
pub mod closed_range;
pub mod open_range;
pub trait Range {
fn new(lower: i8, upper: i8) -> Self;
fn to_string(&self) -> String;
fn contains(&self, number: i8) -> bool;
fn equals(&self, range: Self) -> bool;
fn is_connected_to(&self, range: Self) -> bool;
}
closed_range.rs
と opened_range.rs
には実際の実装がなされています。
closed_rangeの例
// Range trait を利用することを明示
use crate::range::Range;
#[derive(Clone)]
pub struct ClosedRange {
pub lower: i8,
pub upper: i8,
}
impl Range for ClosedRange {
/*
* 実際の実装
*/
}
関連するモジュールのRustファイルの準備とファイル構成を、階層的に管理していくことが求められるそうです。
こちらのサイトに詳しいまとめ等が載っています。
tests
ディレクトリ配下も、モジュールと同じ構成にしてみたのですが、全くテストが走らず。。。
結果、落ち着いた構成は以下のような感じです。
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│ ├── lib.rs
│ ├── main.rs
│ ├── range
│ │ ├── closed_range.rs
│ │ └── open_range.rs
│ └── range.rs
└── tests
├── closed_range.rs ← ディレクトリを掘れず tests 配下
└── open_range.rs ← ディレクトリを掘れず tests 配下
課題2:開区間 by h0ng0yut0 · Pull Request #2 · h0ng0yut0/tddbc-rust-practice
今回は、trait, modとその管理方法 を学べました。次回の課題は何が学べるのか楽しみです。
[これから読んでみたい本]
__一緒に様々なことを学んでいく仲間を募集__しています。
このサイトのお問い合わせなどからご連絡いただけると幸いです。