Josh Kaufman氏によると、正しく学習を行えば20時間で新たなスキルを身につけることができるそうです。
今回は氏の学習方法を参考に、「Rust」について20時間学習を行ってみたので、その結果をまとめてみたいと思います。
なお学習ノートとしての側面が強いので注意してください。
はじめに
学習前の知識や技術
友人とRustをやってみよう、という話になり学習をはじめる。
Go vs. Rust performance comparison: The basicsを少し読んだ程度。
学習目標
-
- Rustの基本的な構文を身につける
- 簡単なCLIアプリケーションを作成する
学習内容
今回の期間中に行った内容です。当初の予定を超えてWeb APIの作成まで進むことはできましたが、完成までは行けませんでした。
-
- Rust The Book
-
- gitのCLIアプリケーション作成
- Actix WebでWeb API作成(途中)
Rustup
Rustはrustupというツールを使用することで簡単にインストール・管理をすることができます。こちらのURLにアクセスすると、プラットフォームに応じたRustのインストール方法が表示されます。
また、Playgroundというオンラインエディタも用意されており、簡単なRustコードならこちらから実行することもできます。
Rust The Book
Rust The Bookは、Rustの概観を基本原理から説明している入門書です。また、非公式の日本語翻訳版も存在します。
今回はこのRust The Bookで気になった部分を紹介します。
useキーワードでパスをスコープに持ち込む
Listing 7-11 も 7-13 もおなじ仕事をしてくれますが、関数をスコープにuseで持ち込む場合、Listing 7-11 のほうが慣例的なやり方です。 関数の親モジュールをuseで持ち込むことで、関数を呼び出す際、毎回親モジュールを指定しなければならないようにすれば、フルパスを繰り返して書くことを抑えつつ、関数がローカルで定義されていないことを明らかにできます。
// Listing 7-11: use でモジュールをスコープに持ち込む
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
fn main() {}
// Listing 7-13: add_to_waitlist 関数をuse でスコープに持ち込む。このやりかたは慣例的ではない
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}
fn main() {}
Rustではサンプルコードにもあるように、関数を直接useすることも、親モジュールを介して持ち込むこともできるようですが、親モジュールを介して持ち込むやり方が慣例的なようです。
それに対して、持ち込む対象が構造体などの場合はまた異なるようです。
一方で、構造体やenumその他の要素をuseで持ち込むときは、フルパスを書くのが慣例的です。 Listing 7-14 は標準ライブラリのHashMap構造体をバイナリクレートのスコープに持ち込む慣例的なやり方を示しています。
// Listing 7-14: HashMapを慣例的なやり方でスコープに持ち込む
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
CLIアプリケーションを開発した後にコードを見直してみたのですが、このルールは忘れてしまいがちでした。しっかりとこのルールを守る必要がある場合は、何らかの静的解析ツールを導入するのが必須かもしれません。
リファクタリングしてモジュール性とエラー処理を向上させる
プログラムをmain.rsとlib.rsに分け、ロジックをlib.rsに移動する。
main関数はプログラムの実行を行い、lib.rsがビジネスロジックを扱うようにすると良いそうです。この構造を採用することで、main関数の責務は、lib.rsのrun関数を呼び出す、runのエラーハンドリングを行う、など小規模にまとまるそうです。
その他のドキュメント
Rustでは、The Book以外にもサンプルコードが多く掲載されているRust By Exampleやアプリケーション単位で解説をしたコマンドラインブックなどのドキュメントも存在します。
structoptでコマンドライン引数を解析する
StructOptは、構造体を定義することで簡単にコマンドライン引数を解析することができるようになるパッケージです。
Maintenance
As clap v3 is now out, and the structopt features are integrated into (almost as-is), structopt is now in maintenance mode: no new feature will be added.
とあります。今後はclapを採用した方が良いかもしれません。
Cargo.tomlの[dependencies]に追加することでインストールすることができます。
[dependencies]
structopt = "0.3"
サンプルコードです。
まず、#[derive]アトリビュートにStructOptを渡します。すると、from_argsという関数が実装されるので、この関数を呼び出すことでコマンドライン引数を解析することができます。
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
struct Opt {
#[structopt(short, long)]
debug: bool,
}
fn main() {
let opt = Opt::from_args();
println!("{:#?}", opt);
}
cargo runコマンドの実行時に、–で区切ることでコマンドライン引数を渡すことができます。
上記サンプルコードの場合は、以下のようになります。
$ cargo run -- -d
Opt {
debug: true,
}
StructOptを使用することで、コマンドライン引数を簡単に解析することができました。
RustのWebフレームワーク Actix Web
Actix Webは、RustのWebフレームワークです。StructOptと同様に、Cargo.tomlの[dependencies]に追加することでインストールすることができます。
[dependencies]
actix-web = "4.3.1"
# サンプルコード用
utoipa = { version = "3", features = ["actix_extras"] }
utoipa-swagger-ui = { version = "3", features = ["actix-web"] }
serde = { version = "1.0", features = ["derive"] }
Actix Webを用いたサンプルコードが以下になります。
/api/v1にアクセスすると、MyObjとして定義したJSONを返すというシンプルな内容です。
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder, Result};
use serde::Serialize;
use utoipa::{OpenApi, ToSchema};
use utoipa_swagger_ui::{SwaggerUi, Url};
#[derive(Serialize, ToSchema)]
struct MyObj {
message: String,
}
#[utoipa::path(
get,
context_path = "/api/v1",
responses(
(status = 200, body = MyObj)
)
)]
#[get("/")]
async fn index() -> Result<impl Responder> {
Ok(HttpResponse::Ok().json(MyObj {
message: "Hello, Actix Web!".to_string(),
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
#[derive(OpenApi)]
#[openapi(paths(index), components(schemas(MyObj)))]
struct ApiDoc;
HttpServer::new(|| {
App::new()
.service(web::scope("/api/v1").service(index))
.service(SwaggerUi::new("/swagger-ui/{_:.*}").urls(vec![(
Url::new("api", "/api-docs/openapi.json"),
ApiDoc::openapi(),
)]))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Cargo.tomlに記述したパッケージのうち、serdeがRustのデータ構造をJSON(等)にシリアライズおよびデシリアライズするためのフレームワークです。
また、utoipaとutoipa-swagger-uiというパッケージを使用することで、API定義をブラウザ上で確認することもできます。
まとめ
今回は「Rust」についての学習を20時間行い、その結果をまとめてみました。
Haskellほどではありませんでしたが、今まで触ってきたPHPやTypeScriptとは大きく異なっているので、プログラミングの視野が広まったような気がします。
今後は第二言語としてRustの知識と技術を伸ばしていけたらな、と思います。