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
Cargo.toml
- コレが Python・Pipenv における
Pipfile
とか、Node.js・npm におけるpackage.json
的な役割のファイル
- コレが Python・Pipenv における
[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]
src/main.rs
- エントリポイントとなるファイルが自動作成されていた
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!
何か実装してみる
何を実装するか迷ったのだが、たまたま「コラッツ予想」というモノを知ったので、コレを計算するプログラムを書いてみた。
- 参考 : コラッツの問題 - Wikipedia
- この Wikipedia のページに「コラッツの数列を計算するプログラミング例」という擬似コードがあり、コレを参考にした
構文などを参考にしたのは以下のページ。
- 参考 : The Rust Programming Language 日本語版 - The Rust Programming Language 日本語版
- 参考 : とほほのRust入門 - とほほのWWW入門
- 参考 : エラー・ハンドリングのキホン | text.Baldanders.info
試行錯誤の末、とりあえず実装できたのは次のようなコード。
//! 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);
}
}
ざっと学んだことは以下のとおり。
fn main()
関数がメインprintln!()
マクロでコンソール出力する{}
がプレースホルダになり、第2引数以降が割り当てられる- マクロと関数は違って、関数はデータを受け取りデータを返すが、マクロは Rust のコードを作る「メタプログラミング」を実現する機能。多用するもんじゃないみたい
let
は定数宣言let mut
とすると変数宣言になる (ミュータブル)
///
(スラッシュ3つ) でドキュメンテーションコメント//!
でファイル行頭などにコメントを書いておくと、コレもドキュメンテーションに記載される- 文字列はダブルクォート、文字はシングルクォート。JS 的ではなく Java 的
- 数値に関する型が細かく別れている。上のコードの中では
u32
という型を使ったu32
は符号なし 32bit の整数値なので、負数や小数は扱えない。最大値は約42億なので、コレを超えるような数が出てくる数値は指定できない- 負数も扱う場合は、符号付きの
i32
などを使う
- コラッツの計算ロジックは
fn collatz()
に切り出した- 「コラッツ予想」というのは、どんな整数でも、決まった計算を繰り返すと 1 になるんじゃね?という数学の未解決問題らしい。計算のルール自体が簡単なので試しに実装してみた
- その数が奇数の場合は、3倍して1を足す
- その数が偶数の場合は、2で割る
- 計算して出てきた数値に対してコレを繰り返してやると、どんな数でもいつかは 1 になる、というワケ
- コマンドライン実行時、第1引数を取得するために色々書いた
use std::env;
という標準ライブラリを持ち込むlet args: Vec<String> = env::args().collect();
で引数を取得するargs.len()
で引数の数を知るlet arg_value = &args[1];
で第1引数を取得する (引数未指定の場合はココでエラーになってしまうので注意)
- コマンドライン実行時の第1引数を取得した時、文字列から数値に変換する部分のコードが面白い
- 定数の型を定義したうえで
parse()
すれば型変換できる unwrap_or_else()
で、型変換の際にエラーが発生した時の処理を書ける。Rust にはいわゆるtry
・catch
がないそうで、こうした独特なやり方でエラーハンドリングする
- 定数の型を定義したうえで
…ということで、次のように実行できる。
# 引数未指定の場合は 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 言語を触ってみる
Go 言語も「例外」の仕組みがなかったりして、どちらも次世代の C 言語的なことをいわれているが、いざ触ってみると、それぞれの言語が対象とする領域が若干違うかな、という感じがする。Rust は Rust が得意な領域で、Go は Go が得意な領域で、それぞれ使っていくことになろう。
- 過去記事 : Deno ちょっと触ってみたメモ
マクロ、所有権、トレイトなど、Rust 特有の概念もあるが、覚えたらスゲー効率的に実装できそうな感がある。cargo
コマンドは test
や doc
など便利なサブコマンドが充実していて、この辺の思想は Rust 製の次世代 Node.js な Deno にも引き継がれている感じ。
新しい言語、というか、自分が知らないパラダイムを学習するのは面白い。気分転換にもなるので、Rust をもう少し触ってみよう。