RustでCHIP-8のエミュレータを書いた

まえにGoで挑戦したCHIP-8エミュレータの実装を今度はRustでやっていました。 github.com/masu-mi/rusty-chip8に置いています。

反省

とりあえずstd::*を一読するべき。 いろいろと学ぶことがたくさんあります。とりあえず下に目を通せば基本的なことはできるようになるはずと思いました。

module メモ
std::option Some(_), Noneのあれ
std::result Ok(T), Err(E)のあれ
std::boxed::Box メモリサイズが不確定な型をヒープに配置して参照をもつために使う
std::rc::Rc 参照カウンタ
std::array 配列作る便利関数がある
std::vec Vec、可変長配列やスタックに使える, len, capacityあってgoのスライスみたいなもの
std::collections 一般的なデータ構造が揃ってる(VecDeque, HashMap, HashSet, BTreeMap, BTreeSet, LinkedList, BinaryHeap)
std::iter forIntoIterator を使った糖衣構文
std::sync::{Arc, Mutex, RwLock, Once} アトミックな参照カウンタ, Mutex, RwLock, Once
std::sync::mpsc キューが提供されてる、try_* がノンブロッキングにやりとりするためのメソッド
std::env 環境変数や引数
std::fs ファイルシステム
std::path パス操作
std::net tcp, udp
std::io Read, Write でGoとほぼ同じ、専用のResult, Errorが定義されてる、sink(), copy(), stdin(), stdout()とか便利関数もある
std::time Duration, Instant, Systemtimeを知ってればOK
std::fmt フォーマット
std::thread スレッド
std::process プロセス

collectionsの中身を変更する

Ram実装で値の書き換えを for ループで実装しようとしたところハマりました。理由は可変参照を使わなかったためです。

forIntoIterator を使ったloopの糖衣構文です。そして Vec, &mut Vec, &VecIntoIterator::into_iter() の返値の型が変わります。

Vec::into_iter()vec::into_iter::IntoIter<T, A>が使われ元の変数から所有権が移ります。そのため値を変更するという目的には使えません。ちなみに vec::into_iter:IntoIter の定義はここです。

Vecの中身を変更したい時には下のように可変参照を使います。

1
2
3
4
5
6
7
8
fn mut_ref_loop() {
    let mut input = vec![1, 2, 3];
    println!("{:?}", input);
    for v in &mut input {
        *v = 4;
    }
    println!("{:?}", input);
}

またIteratorIntoIterator実装しているため下のような書き方も成立します。

1
2
3
4
5
6
7
8
fn iter_mut_loop() {
    let mut input = vec![1, 2, 3];
    println!("{:?}", input);
    for v in input.iter_mut() {
        *v = 4;
    }
    println!("{:?}", input);
}

loop内でlock()を取るとdropされない

したのようにlock()をloop直下で行いsleep()するとロックが解放されず他のスレッドを止めてしまいます。

1
2
3
4
5
6
thread::spawn(move || loop {
    let now = Instant::now();
    let mut m = kkk.pressed.lock().unwrap();
    m.clear();
    thread::sleep(d - (Instant::now() - now))
});

そこでスコープを区切って確保した値の変数のスコープが早く閉じるように変更した。ちゃんと関数に分けた方が良いとは思います。

1
2
3
4
5
6
7
8
thread::spawn(move || loop {
    let now = Instant::now();
    {
        let mut m = kkk.pressed.lock().unwrap();
        m.clear();
    }
    thread::sleep(d - (Instant::now() - now))
});

オプション解析

人気そうだったのでオプション解析ではclapを使いました。 ビルダーパターンで定義もできるのですが手続きマクロによって下のように定義することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    #[clap(short, long)]
    rom: String,
    #[clap(short, long)]
    keyboard_keeptime_ms: u16,
    #[clap(short, long)]
    cpu_hz: u32,
}

logger設定

ロギングでは、インタフェースを提供するlogクレートが人気なようです。そして今回は、これに対応したenv_loggerを使いました。 main()先頭で使いたいロガーのinit_logger()を呼びだします。

1
2
3
fn main() {
    env_logger::init_logger();
}
comments powered by Disqus