Rust で「コラッツ予想」を計算する CLI 作ってみた

過去に Rust のパッケージ管理ツールである、Cargo を使った記事は何個か書いたことがある。

今回は、Rust で実際にプログラミングしてみようと思い、改めて環境構築からやってみた。

先に、完成品は以下に置いている。

目次

WSL で Rust をインストールする

WSL の場合、通常の Linux と同じ要領で Rust・Cargo のインストールができる。以下のワンライナーでセットアップスクリプトを実行し、選択肢では「1」を選択すれば良い。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /home/neo/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

  /home/neo/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /home/neo/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /home/neo/.profile
  /home/neo/.bash_profile
  /home/neo/.bashrc

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1
# 1 を選択した

info: profile set to 'default'
info: default host triple is x86_64-unknown-linux-gnu
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2021-11-01, rust version 1.56.1 (59eed8a2a 2021-11-01)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
 52.9 MiB /  52.9 MiB (100 %)  21.8 MiB/s in  2s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 17.3 MiB /  17.3 MiB (100 %)   9.7 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
 23.5 MiB /  23.5 MiB (100 %)  15.0 MiB/s in  1s ETA:  0s
info: installing component 'rustc'
 52.9 MiB /  52.9 MiB (100 %)  16.0 MiB/s in  3s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-unknown-linux-gnu'

  stable-x86_64-unknown-linux-gnu installed - rustc 1.56.1 (59eed8a2a 2021-11-01)

Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source $HOME/.cargo/env

~/.profile~/.bash_profile~/.bashrc の末尾に何やら追記されているのだが、とりあえず以下の2行を ~/.bashrc に書いておけば良い。

$ export PATH="${PATH}:${HOME}/.cargo/bin"
$ source "${HOME}/.cargo/env"

インストールして PATH が通せたら、以下のようにバージョン確認などできる。

$ rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.56.1 (59eed8a2a 2021-11-01)`

$ rustc --version
rustc 1.56.1 (59eed8a2a 2021-11-01)

$ cargo --version
cargo 1.56.0 (4ed5d137b 2021-10-04)

Cargo でプロジェクトを作ってみる

cargo コマンドは、Node.js でいう npm みたいな感じで、開発プロジェクトを作ったりとかが色々できる。今回は以下のように開発プロジェクト (ディレクトリ) を作ってみた。

$ cargo new --bin practice_rust
     Created binary (application) `practice_rust` package

$ cd ./practice_rust/
$ tree
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files
[package]
name = "practice_rust"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
fn main() {
    println!("Hello, world!");
}

Cargo の場合、cargo run でとりあえずの実行ができるらしいのでやってみたところ、linker cc not found というエラーが出た。

$ cargo run
   Compiling practice_rust v0.1.0 (/PATH/TO/practice_rust)
error: linker `cc` not found
  |
  = note: No such file or directory (os error 2)

error: could not compile `practice_rust` due to previous error

以下の文献によると、Rust のビルドには gcc を使用するのだが、gcc がインストールされていないとこのエラーが出るらしい。

WSL (Ubuntu) の場合は、次のコマンドで gcc をインストールできる。

$ sudo apt install -y build-essential

再実行してみたらうまく行った。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/practice_rust`
Hello, world!

何か実装してみる

何を実装するか迷ったのだが、たまたま「コラッツ予想」というモノを知ったので、コレを計算するプログラムを書いてみた。

構文などを参考にしたのは以下のページ。

試行錯誤の末、とりあえず実装できたのは次のようなコード。

//! Practice Rust : コラッツ予想を計算する CLI ツール

// コマンドライン引数を受け取るための標準ライブラリ
use std::env;

/// メイン関数
fn main() {
  // let は不変。let mut とすると可変 (ミュータブル) になる
  let args: Vec<String> = env::args().collect();
  
  // コマンドライン引数が1つあればそれを利用する
  if args.len() > 1 {
    let arg_value = &args[1];
    // 引数を正の整数値に変換できなければ終了する
    let num: u32 = arg_value.parse().unwrap_or_else(|error| {
      println!("Invalid Arg Value : {:?}", error);
      std::process::exit(1);
    });
    println!("Collatz : {}", num);
    collatz(num);
  }
  else {
    let default_num = 10;
    println!("Collatz : Default {}", default_num);
    collatz(default_num);
  }
  println!("Finished");
}

/// コラッツの数列を計算する関数
fn collatz(num: u32) {
  println!("{}", num);  // ! 付きだとマクロ
  if num % 2 != 0 && num > 1 {
    collatz(3 * num + 1);
  }
  else if num % 2 == 0 {
    collatz(num / 2);
  }
}

ざっと学んだことは以下のとおり。

…ということで、次のように実行できる。

# 引数未指定の場合は 10 という数をコラッツ予想にかける
$ cargo run
   Compiling practice_rust v0.1.0 (/PATH/TO/practice-rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.36s
     Running `target/debug/practice_rust`
Collatz : Default 10
10
5
16
8
4
2
1
Finished

# 以下のように任意の整数値を指定してコラッツ予想の計算ができる
$ cargo run 168
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/practice_rust 168`
Collatz : 168
168
84
42
21
64
32
16
8
4
2
1
Finished

cargo build でビルドすると、シングルバイナリが生成される。

$ cargo build
$ ./target/debug/practice_rust
$ ./target/debug/practice_rust 168

生成された ./target/debug/practice_rust のファイルサイズは 3.7MB 程度だった。

ついでに、以下でドキュメンテーションを生成できる。

$ cargo doc
$ open ./target/doc/practice_rust/index.html

良き!

今回作成したプログラム全量は以下に置いている。

以上

自分は C や C++ をほぼ書いたことがないので、「C っぽさ」ってこういうところなのかな、というのを Rust を通じて学んでいる感じがする。Go 言語と比べるともう少し低級な感じだが、エラーメッセージが比較的分かりやすく助かる。

Go 言語も「例外」の仕組みがなかったりして、どちらも次世代の C 言語的なことをいわれているが、いざ触ってみると、それぞれの言語が対象とする領域が若干違うかな、という感じがする。Rust は Rust が得意な領域で、Go は Go が得意な領域で、それぞれ使っていくことになろう。

マクロ、所有権、トレイトなど、Rust 特有の概念もあるが、覚えたらスゲー効率的に実装できそうな感がある。cargo コマンドは testdoc など便利なサブコマンドが充実していて、この辺の思想は Rust 製の次世代 Node.js な Deno にも引き継がれている感じ。

新しい言語、というか、自分が知らないパラダイムを学習するのは面白い。気分転換にもなるので、Rust をもう少し触ってみよう。