「TDDBCの課題をRustで実施し、言語仕様を学んでいこう!」というシリーズの記事です。
[ひとことで言うとこんな記事]
以下2点を学ぶ事ができます
[こんなひとにおすすめ]
[目次]
インストール等の記事は、以下記事をご覧ください。
Rustを0から学んでみた 〜Part.0 準備/概要 編〜
準備
cargo new tddbc-rust-practice --bin
今回ご紹介する課題の内容です。
pushした私のリポジトリのtreeです。
.
├── Cargo.toml
├── README.md
├── src
│ ├── lib.rs ←コレ
│ └── main.rs
└── tests
└── lib.rs
src/lib.rs
に準備することでいい感じになるそうです。
By default, everything in Rust is private, with two exceptions: Associated items in a pub Trait are public by default; Enum variants in a pub enum are also public by default. When an item is declared as pub, it can be thought of as being accessible to the outside world. For example:
// Declare a private struct struct Foo; // Declare a public struct with a private field pub struct Bar { field: i32, } // Declare a public enum with two public variants pub enum State { PubliclyAccessibleState, PubliclyAccessibleState2, }
trait
とenum
に関しては例外的にpublicがデフォルトだそうです。
他言語で使われるいわゆるClassというものを実現することができます。
実際に書いてみたソースコードはこちらになります。(GitHubのページへ飛びます)
GitHub - h0ng0yut0 tddbc-rust-practice/lib.rs
メンバ変数定義のようなものですね。「閉区間は下限と上限を持つ」という例が以下です 。
// lib.rs
#[derive(Clone)]
pub struct ClosedRange {
pub lower: i8,
pub upper: i8,
}
上記「基本 Private (例外アリ)」で Private Public の話をしましたが、 structのメンバもデフォルトpublicでよくね? とか思うんだけどどうなんだろう。。。
また、#[derive(Clone)]
を記述することにより、同じ値を持った構造体をcloneすることができる。(cloneしないで普通に代入すると、Rustのメモリ管理方法によりスコープ外とみなされますからね〜)
// main.rs
let sample_closed_range = closed_range::ClosedRange::new(1, 5);
let sample_closed_range_same = sample_closed_range.clone();
structの持つ振る舞いを定義しています。
#[derive(Clone)]
pub struct ClosedRange {
pub lower: i8,
pub upper: i8,
}
impl ClosedRange {
pub fn new(lower: i8, upper: i8) -> ClosedRange {
match (lower, upper) {
(lower, upper) if lower > upper => Err("下限と上限の値が不正です".to_owned()),
_ => Ok(ClosedRange { lower: lower, upper: upper }),
}
.unwrap() // 生成時にErrの場合panic!させちゃう
}
pub fn to_string(&self) -> String {
format!("[{},{}]", self.lower, self.upper)
}
}
今回 new
と to_string
を定義させていただきました。impl自体にpub
を書かずに、fnにつけるらしいです。
実際にこの課題で書いてみたソースコードは以下になります。
GitHub - h0ng0yut0 tddbc-rust-practice/lib.rs
.
├── Cargo.toml
├── README.md
└── src
├── lib.rs ←ココ(本機能と同じ場所)
└── main.rs
以下のように記述できます。
pub mod closed_range {
// 実際に定義したstructやimplを実装
#[cfg(test)] // 【1】テストコード記載場所を示す
mod tests {
use super::*; // 【2】外部モジュールで定義したものを利用できるようにする
#[test] // 【3】テストをfnで記述していく
fn test1() {
}
#[test] // 【3】テストをfnで記述していく
fn test2 (){
}
}
}
利点としては、 実装箇所と同じ場所にテストコードを記述することで、小さいサイズの実装であれば見通しが良い というところでしょうか。
はじめこちらの場所で実装していたのですが、本機能の実装を見通しよくするために、別の場所でテストコードを管理することとしました。
.
├── Cargo.toml
├── README.md
├── src
│ ├── lib.rs
│ └── main.rs
└── tests
└── lib.rs ← コレ
以下のように記述できます。
// 【1】各々ライブラリ等をインポートする必要がある
extern crate tddbc_rust_practice;
use tddbc_rust_practice::closed_range::ClosedRange;
#[test] //【2】テストをfnで記述していく
fn test1(){
}
#[test] //【2】テストをfnで記述していく
fn test2(){
}
1個目との違いは以下の点です。
#[cfg(test)]
の記載は不要利点としては、同じ場所にテストコードを書かないので 本実装の見通しが良いこと でしょうか??
left == right
になる。以上。(equal)
left != right
になる。以上。(not equal)
hogeはtrue
になる。以上。
今回の例でいうと、下限と上限の入力値がおかしい場合にエラーを引き起こすように設計しているので、テストコードは以下のようになります。
実装
impl ClosedRange {
pub fn new(lower: i8, upper: i8) -> ClosedRange {
match (lower, upper) {
(lower, upper) if lower > upper => Err("下限と上限の値が不正です".to_owned()),
_ => Ok(ClosedRange { lower: lower, upper: upper }),
}
.unwrap() // 生成時にErrの場合panic!させちゃう
}
}
テストコード
#[test]
#[should_panic(expected = "下限と上限の値が不正です")]
fn new_error() {
// 下限、上限の入力が不正な場合
let lower = 5;
let upper = 1;
ClosedRange::new(lower, upper);
}
以上のように書けば、どんな内容のpanic!
かもテストできますね。
課題1:閉区間 by h0ng0yut0 · Pull Request #1 · h0ng0yut0/tddbc-rust-practice
今回は、struct, impl, テストコード等を学べました。次回の課題は何が学べるのか楽しみです。
[これから読んでみたい本]
__一緒に様々なことを学んでいく仲間を募集__しています。
このサイトのお問い合わせなどからご連絡いただけると幸いです。