Edited at

ことわざプログラミング: 2. "蛙の子は蛙"

More than 3 years have passed since last update.


はじめに

"ことわざをプログラミング"することで Rust を勉強していきます.


本日のお題: 蛙の子は蛙


カエルは、子供の頃はおたまじゃくしで親とは似ても似つかない姿だが、おたまじゃくしも成長すれば親と同じようにカエルになることから。

「蛙の子は蛙の子」とも。

http://kotowaza-allguide.com/ka/kaerunokowakaeru.html



本日の学び


  • クラスの定義の仕方

  • 借用とメソッドについて

  • 多重ループの抜け方


GitHubレポジトリ

https://github.com/skyshk/frog_bears_frog


仕様


  1. ある一定の量の蛙が生息できる池がある

  2. 蛙は池に飛び込むことができる

  3. 池に生息する蛙は一定間隔ごとに子供を産む(そいつらももちろん蛙)

  4. 産まれた子蛙たちもどんどん池に飛び込む

  5. 池が満杯になったら終了


コード全体

// Proverb-Programming: "A frog bears a frog"

use std::io;

// 蛙クラス
struct Frog {
is_frog: bool
}

impl Frog {
// コンストラクタ
pub fn new() -> Frog {
Frog{
is_frog: true
}
}
pub fn is_frog(&self)->bool{
self.is_frog
}
// 蛙は蛙を産む
pub fn bears_child_frog (&self) -> Frog{
Frog{
is_frog: true
}
}
// 蛙は池に飛び込もうとするが,池がいっぱいだったら飛び込めない
pub fn jump_into(self, pond: &mut Pond) -> bool{
let pond_has_space: bool = !(pond.is_fulfilled());
if pond_has_space {
pond.accept(self);
}
pond_has_space
}
}

// 池クラス
struct Pond {
pub n_frog_capacity: u32,
pub frogs_living_in: Vec<Frog>
}

impl Pond {
// コンストラクタ
pub fn new(n_frog_capacity: u32) -> Pond {
Pond{
n_frog_capacity: n_frog_capacity,
frogs_living_in: Vec::new()
}
}

pub fn count_frogs(&self) -> u32{
self.frogs_living_in.len() as u32
}

pub fn is_fulfilled(&self) -> bool{
self.count_frogs() >= self.n_frog_capacity
}

pub fn accept(&mut self, frog: Frog) {
self.frogs_living_in.push(frog);
}
}

fn main() {

// 池の蛙容量を標準入力から
println!("Please input the frog capacity of the pond:");
let mut n_frog_capacity_string = String::new();
io::stdin().read_line(&mut n_frog_capacity_string)
.expect("cannot read the line.");
n_frog_capacity_string = n_frog_capacity_string.trim_right().to_string();
let n_frog_capacity: u32 = n_frog_capacity_string.parse().unwrap();

// 池のインスタンスを作成
let mut the_pond = Pond::new(n_frog_capacity);

// 第一匹目の蛙が池に飛び込む
let first_frog = Frog::new();
first_frog.jump_into(&mut the_pond);

let mut epochs = 0;
'outer: loop{
epochs += 1;
let mut new_children_frogs: Vec<Frog> = Vec::new();
// 池に生息する蛙は一定間隔ごとに子供を産む
for frog in & (the_pond.frogs_living_in) {
let child_frog = frog.bears_child_frog();
if child_frog.is_frog(){
new_children_frogs.push(child_frog)
}
}
// 産まれた子蛙たちもどんどん池に飛び込む
for frog in new_children_frogs{
let could_jump_into = frog.jump_into(&mut the_pond);
// 池が満杯になったら終了
if !could_jump_into{
println!("Epochs: {:5}, Frogs: {:5}", epochs, the_pond.count_frogs());
break 'outer;
}
}
println!("Epochs: {:5}, Frogs: {:5}", epochs, the_pond.count_frogs());
}
println!("The pond has no space any more!");
}


実行結果

$./frog_bears_frog

Please input the frog capacity of the pond:
10000 # 池に飛び込むことのできる蛙の量を指定する
# 蛙が蛙を産んでどんどん増えていく
# 今は 1 epoch に 1 匹ずつしか産めないので,倍々ゲーム(2^epochのオーダー)で増える
Epochs: 1, Frogs: 2
Epochs: 2, Frogs: 4
Epochs: 3, Frogs: 8
Epochs: 4, Frogs: 16
Epochs: 5, Frogs: 32
Epochs: 6, Frogs: 64
Epochs: 7, Frogs: 128
Epochs: 8, Frogs: 256
Epochs: 9, Frogs: 512
Epochs: 10, Frogs: 1024
Epochs: 11, Frogs: 2048
Epochs: 12, Frogs: 4096
Epochs: 13, Frogs: 8192
Epochs: 14, Frogs: 10000
The pond has no space any more!
# 池の許容量を超えたところで終了


コード解説

クラスの定義の仕方はこちらを参考にしました.

クラスの定義は,属性はstructで,メソッドはimplで分離して定義するのはGo言語とかもそうですね.

特につまづいたところは「所有権」「借用」まわりです.

Rust の重要概念なので自分で試行錯誤しながら覚えたいです.

ここが詳しいドキュメントです.


for 文での借用

以下のコードでは,the_pond.frogs_living_in配列の要素をfrogに格納しつつループを回しています.

for frog in & (the_pond.frogs_living_in) {

let child_frog = frog.bears_child_frog();
if child_frog.is_frog(){
new_children_frogs.push(child_frog)
}
}

for frog in the_pond.frogs_living_in としてしまうと,

the_pond.frogs_living_inの要素の所有権をfrogに永久に上げてしまうことになり,

forループのあとthe_pond.frogs_living_inの要素にアクセスできなくなってしまいます.

for frog in & (the_pond.frogs_living_in) としてあげることで,

frogthe_pond.frogs_living_inの要素の所有権を借用しているだけになり,

forループから抜けるとthe_pond.frogs_living_inの要素の所有権は復活するため,

各要素にアクセスすることも可能です.

この面倒な所有権の概念,ドキュメントでは「大丈夫,すぐに慣れるよ」的な事が書いてありますが,ほんとうでしょうか...


メソッドでの借用

所有権を気にする機会が多いのは関数やメソッドの引数として変数を渡すのようです.

原則的には,次のような使い分けなのでしょう.

* 引数として渡す変数はもう二度と使わない→ 何もつけず受け渡す

* 引数として渡す変数は今後も使いたい.関数の中では参照するだけ → 参照 & をつけて受け渡す

* 引数として渡す変数は今後も使いたい.関数の中では変更を加える → 可変参照 &mut をつけて受け渡す

今回のコードの中だと,Pondクラスの中の次の2つのメソッドが対照的です.

pub fn count_frogs(&self) -> u32{

self.frogs_living_in.len() as u32
}

pub fn accept(&mut self, frog: Frog) {
self.frogs_living_in.push(frog);
}

前者はメンバ変数frogs_living_inの値を参照するだけなので,引数self&をつけて受けとります.

後者はメンバ変数frogs_living_inの値にfrogを加えるという変更を加えたいので,引数self&mutをつけて受けとります.

一方引数frogの方は,何もつけていないため完全に所有権をいただいちゃっています.これではacceptを呼んだ外側ではその後frogに渡した変数を使うことができません.

// 蛙は池に飛び込もうとするが,池がいっぱいだったら飛び込めない

pub fn jump_into(self, pond: &mut Pond) -> bool{
let pond_has_space: bool = !(pond.is_fulfilled());
if pond_has_space {
pond.accept(self);
// self: Frog は所有権をpond.accept()に完全に渡してしまっているので,このあとに
// self.is_frog();
// など呼ぼうとしてもコンパイルが通らない
// `self` moved here because it has type `Frog`, which is non-copyable
}
pond_has_space
}

これは別に意図したわけではなく,Pond::accept()の中の self.frogs_living_in.push(frog);frogpushするには参照を渡すだけではダメでしたので,Pond::accept()frogそのものを渡すしかありませんでした.


コンパイル通らず

pub fn accept(&mut self, frog: &mut Frog) {

self.frogs_living_in.push(frog);
// コンパイル通らない
// src/main.rs:57:35: 57:39 error: mismatched types:
// expected `Frog`,
// found `&mut Frog`
// (expected struct `Frog`,
// found &-ptr) [E0308]
// src/main.rs:57 self.frogs_living_in.push(frog);
}

なんかうまく実装できていない気がします.

追記:こちらのQiita記事には,以下のようにあります.


配列(Vec)にpushで値を追加すると、値の所有権は配列に移動する。

...

配列に値を追加する際には値を所有権ごと受渡して、後でその値を使う場合は配列から借用して使うイメージである。


ということは,「あるオブジェクトを複数の配列に同時に追加する」というような使い方はしないということでしょうか? "ビュー" のような使い方ができる配列はあるのでしょうか...今後実験が必要です.


多重ループの抜け方

一重のループから抜ける際は,break;を使えばよいのですが,

多重のループを一気に抜けたい際は,break;だけでは力不足です.

loop {

for .. {
// ここでループ全体から抜けたい!
}
}

Rust ではループ(スコープ?)に名前をつけられて,break;で抜けるべきスコープを指定できるようです.

http://rustbyexample.com/flow_control/loop/nested.html

'outer: loop{

...
// 産まれた子蛙たちもどんどん池に飛び込む
for frog in new_children_frogs{
let could_jump_into = frog.jump_into(&mut the_pond);
// 池が満杯になったら終了
if !could_jump_into{
...
break 'outer; // outerまで一気に抜ける
}
}
println!("Epochs: {:5}, Frogs: {:5}", epochs, the_pond.count_frogs());
}


最後に

「この書き方は普通じゃない」「こんなエレガントな書き方があるよ」というアドバイスがありましたらぜひコメントや編集リクエストをお願いします.

所有権・借用まわりはまだまだ勉強ですね.

次: "ペンは剣よりも強し" で trait の使い方を勉強する