Rust公式ドキュメントをDockerコンテナ上で進めてみる（1. 事始め, 2.数当てゲーム）

#はじめに
タイトルの通りです。
Rustは初めてです。
コンテナの使い方として、以下を考えましたが、今回のコンテナをRustの学習以外で使うことはないため、前者でいくことにしました。

- FROM:rustとする
- FROM:ubuntuなどにし、rustをその中でインストールする方法

前者の場合、Rustイメージが利用しているosが使われます。使っているosはrustのDockerfileで確認。
https://github.com/rust-lang/docker-rust

#参考にしたもの
Rust公式ドキュメント(TheRustProgrammingLanguage日本語版)：https://doc.rust-jp.rs/book-ja/title-page.html
DokerHub(Rust)：https://hub.docker.com/_/rust

##1.1 インストール
公式ドキュメント：https://doc.rust-jp.rs/book-ja/ch01-01-installation.html
今回はRustイメージを使ってしまうので、ここは今回の勉強ではスキップ。
代わりに、dockerHub上にRustのimageがあることを確認して、当初の予定通りコンテナが使えることを確認します。

> DokerHub(Rust)：https://hub.docker.com/_/rust

少し下にスクロールしたところDockerfileとビルドコマンドのサンプルがありました。

```dockerfile:DockerHub上のRustのDockerfileのサンプル
FROM rust:1.31

WORKDIR /usr/src/myapp
COPY . .

RUN cargo install --path .
CMD ["myapp"]
```
サンプルがあるならと、バージョンのみ変えてこの通りに記述してみましたがbuildでエラーになりましたのでみなかったことにします。

```bash:現段階ではCargo.tomlがなんだかわからん
$ docker build -t my-rust-app .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM rust:latest
 ---> 8d0d57072949
Step 2/5 : WORKDIR /usr/src/myapp
 ---> Using cache
 ---> 6015e37ea342
Step 3/5 : COPY . .
 ---> 66589dbef629
Step 4/5 : RUN cargo install --path .
 ---> Running in b3cdc5e8eece
error: `/usr/src/myapp` does not contain a Cargo.toml file. --path must point to a directory containing a Cargo.toml file.
```

##1.2 Hello, World!
公式ドキュメント：https://doc.rust-jp.rs/book-ja/ch01-02-hello-world.html
Dockerfileを作成します。一部、DockerHub上のサンプルに合わせています。

```dockerfile:Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects/hello_world
COPY . .

RUN cd /usr/src/projects/hello_world && \
    rustc main.rs
CMD ["./main"]
```

コンテナにコピーするmain.rcを作成します。

```rust:main.rc
fn main() {
    // 世界よ、こんにちは
    println!("Hello, world!");
}
```
ビルドして実行

```bash
$ docker build -t my-rust-app .
$ docker run -it --rm --name my-running-app my-rust-app
Hello, world!
```

##1.3 Hello, Cargo!
公式ドキュメント：https://doc.rust-jp.rs/book-ja/ch01-03-hello-cargo.html
Dockerfileを編集していきます。

```dockerfile:Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects

RUN cd /usr/src/projects && \
     cargo new hello_cargo --bin && \
     cd /usr/src/projects/hello_cargo && \
     cargo build

CMD ["/usr/src/projects/hello_cargo/target/debug/hello_cargo"]

```

ビルドしたところUSERを指定してと言われる。

```bash
$ docker build -t my-rust-app .
（中略）
Step 5/7 : RUN cd /usr/src/projects &&     cargo new hello_cargo --bin
 ---> Running in 1f2404887764
error: Failed to create package `hello_cargo` at `/usr/src/projects/hello_cargo`

Caused by:
  could not determine the current user, please set $USER
```
RUN,CMDをコメントアウトしてコンテナ内に入って実行しても同様。
Dockerはユーザーを指定しないとrootでログインしますが、echo $USERは空。

```bash
$ docker run -it --rm --name my-running-app my-rust-app
root@126e1954fec0:/usr/src/projects# cargo new hello_cargo --bin
error: Failed to create package `hello_cargo` at `/usr/src/projects/hello_cargo`

Caused by:
  could not determine the current user, please set $USER
root@126e1954fec0:/usr/src/projects# echo $USER

```
環境変数のUSERを定義するようDockerfileを修正します。

```dockerfile:Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects

ENV USER=root
RUN cd /usr/src/projects && \
     cargo new hello_cargo --bin && \
     cd /usr/src/projects/hello_cargo && \
     cargo build

CMD ["/usr/src/projects/hello_cargo/target/debug/hello_cargo"]

```

ビルドして実行

```bash

$ docker build -t my-rust-app .
$ docker run -it --user root --rm --name my-running-app my-rust-app
Hello, world!
```
##2.数当てゲームをプログラムする
公式ドキュメント：https://doc.rust-jp.rs/book-ja/ch02-00-guessing-game-tutorial.html

まず初めに全体を確認します。
以下ファイルの修正が必要そうなので、ローカルで作成したものをCOPYする方式をとります。

- src/main.rc
- Cargo.toml

コンテナにコピーするmain.rcとCargo.tomlを作成します。
なお、ローカルで作成するもののフォルダ構成。

```
guessing_game
 - src/main.rc
 - Cargo.toml
```

```rust:guessing_game/src/main.rc
extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
```

```toml:guessing_game/Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["root"]
edition = "2018"

[dependencies]
rand = "0.3.14"
```

Dockerfileを作成します。一部、DockerHub上のサンプルに合わせています。
cargo init を使っているのは既にあるディレクトリでcargo newは使えないため。

```dockerfile:Dockerfile
FROM rust:latest
WORKDIR /usr/src/projects
COPY . .

# 2.数当てゲームをプログラミングする
RUN cargo init guessing_game --bin && \
    cd /usr/src/projects/guessing_game && \
    cargo build
WORKDIR /usr/src/projects/guessing_game
CMD ["cargo", "run"]
```
ビルドしたところcargoのエラーになりました

```bash
$ docker build -t my-rust-app .
（中略）
 ---> Running in 13aa45df5d36
error: `cargo init` cannot be run on existing Cargo packages
The command '/bin/sh -c cargo init guessing_game --bin &&     cd /usr/src/projects/guessing_game &&     cargo build' returned a non-zero code: 101

```
どうもcargo initは既存のディレクトリやソースを使うことはできてもCargo.tomlがあるとダメみたいです。
大人しく、cargo newしたあと、COPYで上書きする形にします。


```dockerfile:修正後のDockerfile
FROM rust:latest
WORKDIR /usr/src/projects

# 2.数当てゲームをプログラミングする
RUN cargo new guessing_game --bin
COPY . .
RUN cd /usr/src/projects/guessing_game && \
    cargo build
WORKDIR /usr/src/projects/guessing_game
CMD ["cargo", "run"]
```



ビルドし問題なくプログラムが稼働することを確認

```bash
$ docker build -t my-rust-app .
$ docker run -it --rm --name my-running-app my-rust-app
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
0
You guessed: 0
Too small!
Please input your guess.
100
You guessed: 100
Too big!
Please input your guess.
97
You guessed: 97
You win!
```
##おまけ
1.1 インストールで出ていたDockerHubのrustテンプレートのビルド時に発生した以下のエラーを解消したい場合

```dockerfile
FROM rust:latest
WORKDIR /usr/src/myapp
COPY . .

##追加###############
ENV USER=root
RUN cargo init .
####################

RUN cargo install --path .
CMD ["myapp"]
```
参考：https://kajirikajiri.gitbook.io/gitbook/untitled

#おわりに
Docker上で言語の勉強をしていますがローカルのパソコンの環境を汚さなくて良いので、なんども作り直しができるのは本当に良いですね。
また、USERの件など、ドキュメントをそのままなぞっているだけでは気付きづらいRustの仕様もDocker上で進めていることによりエラーが出たりして思わぬところで理解が深まる効果が得られています。

なお、実際は、FROMとWORKDIRの定義といった最低限のDockerfileのみでコンテナを作成してコンテナに入り、ドキュメントの通りにコマンド実行したり作成されたものの中身など見ながら進めています。
問題なさそうになったら、Dockerfileを更新して最終的にCMDで同じ結果になることを確認する手順を踏んでいます。
Rustの勉強だけならDockerfileを更新する必要ないのですが、Dockerの仕様の勉強にもなるためしばらく続行しようと思います。