オフラインリアルタイムどう書くE27の参考問題の実装例(Rust)

オフラインリアルタイムどう書く E27の参考問題「灯りと鏡」の実装例を、Rust で。

問題 : http://nabetani.sakura.ne.jp/hena/orde27ligmir/
実装リンク集 : https://qiita.com/Nabetani/items/0b2f459ec128c89948f4

次回のイベントは 12/8。
詳しくは
https://yhpg.doorkeeper.jp/events/82699
を御覧ください。
まだ問題も決まってないけど、 Rust での実装例を用意したいと思っている(未定) ので、Rust 好きの方も是非。

で。
実装例は以下の通り。

```Rust:Rust
const W: isize = 5; // width
const H: isize = 5; // height
const PWS: isize = W + 1; // positive width with separator
const NWS: isize = -PWS; // negative width with separator

fn pos_to_xy(pos: isize) -> (isize, isize) {
    (pos % PWS, pos / PWS)
}

fn step(curpos: isize, dir: isize) -> Option<isize> {
    let (x, y) = pos_to_xy(curpos);
    let okay = match dir {
        NWS => 0 < y,
        PWS => y + 1 < H,
        -1 => 0 < x,
        1 => x + 1 < W,
        _ => panic!("unexpected direction"),
    };
    if okay {
        Some(curpos + dir)
    } else {
        None
    }
}

fn mirror0(dir: isize) -> isize {
    match dir {
        PWS | NWS => dir / PWS,
        1 | -1 => dir * PWS,
        _ => panic!("unexpected direction"),
    }
}

fn run(src: &String, passed: &mut Vec<isize>, mut curpos: isize, mut dir: isize) {
    for _i in 0..(W * H * 2) {
        passed.push(curpos);
        let nextpos_opt = step(curpos, dir);
        match nextpos_opt {
            Some(nextpos) => {
                let nextcell = src.chars().nth(nextpos as usize).unwrap() as u8 as char;
                dir = match nextcell {
                    '.' | 'Y' => dir,
                    '0' => mirror0(dir),
                    '1' => -mirror0(dir),
                    'x' => return,
                    _ => panic!("unexpected direction"),
                };
                curpos = nextpos;
            }
            _ => {
                return;
            }
        }
    }
}

fn solve(src: &String) -> String {
    let you = src.find('Y').unwrap() as isize;
    let mut passed = Vec::<isize>::new();
    run(src, &mut passed, you, -6);
    let mut places = passed
        .iter()
        .map(|pos| {
            let (x, y) = pos_to_xy(*pos);
            ((x + y * W + 'a' as isize) as u8 as char).to_string()
        }).collect::<Vec<String>>();
    places.sort_unstable();
    places.dedup();
    places.join("")
}

fn test(num: i32, src: String, expected: String) {
    let actual = solve(&src);
    let okay = actual == expected;
    println!(
        "{}, {}, src: {}, act: {}, exp: {}",
        num,
        if okay { "ok" } else { "**NG**" },
        src,
        actual,
        expected
    );
}

macro_rules! test {
    ($n:expr, $x:expr, $y:expr) => {
        test($n, $x.to_string(), $y.to_string());
    };
}

fn main() {
    test!(0, "x...x/.1.0./..0../.Y.../0..x.", "ghilnqs");
    test!(1, "..Y../...../...../...../.....", "c");
    test!(2, "..x../..Y../...../...../.....", "h");
    test!(3, "..Y.x/..1x0/11.../....0/1..1.", "c");
    // 中略
    test!(
        45,
        "1...0/.1.0./..1../..01./Y0..1",
        "abcdefghijklmnopqrstuvwxy"
    );
}
```

いつもどおりテストの大半は省略。

問題が簡単だったせいか、わりとすんなり書けた。

今回書き方がわからなかったのは

```Rust:Rust
    let okay = match dir {
        MWS => 0 < y,
        PWS => y + 1 < H,
        -1 => 0 < x,
        1 => x + 1 < W,
        _ => panic!("unexpected direction"),
    };
```

の部分。本当は

```Rust:Rust
    let okay = match dir {
        -(W+1) => 0 < y,
        (W+1) => y + 1 < H,
        -1 => 0 < x,
        1 => x + 1 < W,
        _ => panic!("unexpected direction"),
    };
```

って書きたかったんだけど、エラーだった。
`=>` の左辺に式は書けないのかと思って定数を書いたら通った。そういうものか。

あと、Rust 全般についての感想。

すぐにコンパイルエラーになって、そこは怖いんだけど、エラーメッセージはわりと親切だしわかりやすいと思う。

今回は、コンパイルエラーが出なくなったらいきなり動いて、いきなり全部テスト通った。びっくりした。恐れ入った。

VSCode で書いているんだけど、環境は今ひとつかな。
Go みたいに

* まずいところは下線が付く
* 保存時に勝手にフォーマット

というようになると幸せになると思うけど、拡張機能の探し方が悪いのかそうはなっていない。残念。

※皆様はどんな エディタ / IDE で Rust 書いてますか？