• 作成:
  • 更新:

Rust超初心者向けチュートリアル,ツールとマクロの紹介

社内向けに発表した資料を多少改変してコミュニティに還元します.

初心者向けガイドなので既に書いてるって人は見なくても良いと思います.

実際に手を動かしてツールをインストールしてもらうことを推奨します.

適当に動かしたい人向け

Rust Playground

webサーバがRustコードを実行してくれます.

Rustのダウンロード数100位までのライブラリが使えるので,大抵のサンプルはインストール不要でここで実行できます.

shareしたコードは自動的にgistにアップロードされるのでそこだけは注意.

share機能でパーマリンクで簡単にコードをシェアできるので,TwitterやGitHubやChatworkでソースを送り合うのに便利です.

rustup

rustup.rs - The Rust toolchain installer

curl https://sh.rustup.rs -sSf | sh

システムの方法で入れたいとかはnightlyの扱いが地獄なので諦めてrustupを使った方が良いです.

stableかnightlyか

nightlyは不安定版なので一般ユーザがnightlyの機能を利用するべきではありません.

しかしASTなど内部構造を触れるAPIは不安定なので,Rustの開発ツールはよくnightlyで開発されています.

どうせnightlyが必要になるのでnightlyを選択して,業務コードではnightlyの機能使わないのもありだと思います.容量削減にもなります.

nightlyの機能を使うにはfeatureマクロをソースコードに書く必要があるため,うっかりnightlyの機能を使ってしまうということもないでしょう.

追記:nightly上で安定化された場合などにうっかり使ってしまう可能性があるそうです…stableで問題ない人はそちらの方が良いでしょう.

後からrustup default stableのように簡単に変更できます.

パスの追加

パスは.profileに追記されるのでそのへん気にしてない人はそのままで追加されます.

自分はパラノイアなので自分で

~/.profile

export PATH=$PATH:~/.cargo/bin

って書いてます.

source ~/.cargo/env

でも良さそうです.


.zshrcとかに書くとシェル以外のシステムがrustcを認識できずにうまくいきません.

cargoとかrustupのシェル補完を有効にしたいときは,zshなら以下で出来ます.bashでも似たようなことが出来るはずです.

mkdir -p /tmp/$USER-zsh-completions/
if hash rustup 2>/dev/null; then
    rustup completions zsh > /tmp/$USER-zsh-completions/_rustup
fi

fpath=(
    /tmp/$USER-zsh-completions/
    $fpath)

if hash rustc 2>/dev/null; then
    fpath=($(rustc --print sysroot)/share/zsh/site-functions $fpath)
fi

たまにrustupでコンポーネントがインストールできなくなる時があります

error: component 'clippy' for target 'x86_64-unknown-linux-gnu' is unavailable for download

Rustup packages availability on x86_64-unknown-linux-gnu

などを見て上流がバグってるか確かめましょう.

バグっていたら,rustup install nightly-2019-02-08のように正常なツールチェインをインストールしましょう.

参考: https://rust-jp.slack.com/archives/C8FLSR5F1/p1550128316010700

rustfmt

rust-lang/rustfmt: Format Rust code

rustup component add rustfmt

Goで言うgofmt, C++で言うclang-formatに相当します.

チーム開発するなら絶対に入れて下さい.

ファイル保存時に動くようにしてください.Emacsだとrustic-modeというのが自動で設定してくれます.Gentoo上のEmacsでまともなRust環境を構築しました,バグ報告で問題が解決しました - ncaq

clippy

rust-lang/rust-clippy: A bunch of lints to catch common mistakes and improve your Rust code

rustup component add clippy

コンパイラ警告がカバーしない範囲の警告を出してくれます.

let foo_regex = Regex::new("foo").unwrap();

みたいなの書いたら「それ正規表現使わずにString::containsでええやろ」と警告を出してくれます.

rls

rustup component add rls rust-analysis rust-src

LSP(Language Server Protocol)についてはご存知ですか?

Langserver.org

これまでは言語(処理系)とテキストエディタの組み合わせ全てに,リアルタイムエラーチェックや補完機構を実装する必要がありました.

例えばRust, TypeScript, Scala, PHP, C#の5言語を,Emacs, Vim, VSCode, Atomの4テキストエディタに対応させようとすると,作る必要のあるプロダクトは5*4=20ですね.

LSPはこの問題を解決して,言語にLSPバックエンドを実装して,テキストエディタにLSPフロントエンドを実装して,LSPで通信することで組み合わせの爆発を防ぎます.

さっきの例だと実装数は5+4=9で済みます.


rlsはRustのLSPバックエンドです.

よく使う機能として,リアルタイムエラーチェック,補完,フォーマット,警告に対応したコード自動修正などがあります.

EmacsのLSPフロントエンドはlsp-modeとeglotが有名で,私はシンプルなeglotを使っています.

rustupのコンポーネントのインストールを毎回するのが面倒くさい問題の解決

私はこういうシェルスクリプトを書いて実行してます.

#!/usr/bin/env zsh

world="
clippy
rls
rust-analysis
rust-src
rustfmt
"

echo $world|xargs rustup component add

cargo watch

passcod/cargo-watch: 🔭🚢 Watches over your Cargo project's source

インストール方法

cargo install cargo-watch

使い方

cargo watch -x check

でファイルに変更がある度にシェル上でチェックが走ります.

cargo edit

killercup/cargo-edit: A utility for managing cargo dependencies from the command line.

cargo install cargo-edit

Cargo.tomlのaddupdateをコマンドラインで行なえます.

マクロ

マクロ https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/macros.html

よく紹介されていて多用されているマクロを紹介します.

panic

プログラム(スレッド)がどうしようもない状態に陥ってクラッシュさせるしかない時に使いましょう.

try

今は?演算子が安定化したので使う必要はありません.後で述べます.

unreachable

論理的に絶対この分岐に行かないはずだと思う場所に設置しましょう.

panicより短い.

unimplemented

型チェックをごまかしてとりあえずコンパイル通したい時に置いて後で実装しましょう.

TODOとか書くより検索性に優れています.

dbg

dbg!(error);

eprintln!("error: {}", error);

ほぼ等価として扱えます.

更にdbg!は値を返すのでdbg!dbg!を埋め込んで内部でトレースすることも出来ます!Haskellのtraceと似たような使い方が出来るというわけですね.

env_loggerとは併用出来ないのでプリントデバッグ専用に使いましょう.

?演算子

use std::fs::File;
use std::io::prelude::*;
fn foo1() -> std::io::Result<String> {
    match File::open("foo.txt") {
        Err(err) => return Err(err),
        Ok(mut file) => {
            let mut contents = String::new();
            match file.read_to_string(&mut contents) {
                Err(err) => return Err(err),
                Ok(_) => {
                    assert_eq!(contents, "Hello, world!");
                    Ok(contents)
                }
            }
        }
    }
}

use std::fs::File;
use std::io::prelude::*;
fn foo2() -> std::io::Result<String> {
    let mut file = File::open("foo.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    assert_eq!(contents, "Hello, world!");
    Ok(contents)
}

まともになりました.


ちなみに現実的にはファイルを読み込んで文字列にする関数はstd::fs::read_to_string - Rustとして標準ライブラリにあります.


Resultの関数にOption返すメソッドやResultのエラーの型が違うメソッドが混ざった場合.

  • Option: ok_orを使って何かErrを返す
  • Result: orを使って同じ型のErrを返すかmap_errで型を変換する

が最適解のようですね.

質疑応答: 正規表現の例とかでunwrapって避けられないの?

マッチとかの状況ではmatchif letを使ってきちんとマッチしたかどうか確認はする必要はあります.

正規表現ビルドでunwrapしないといけないのはどうせ起動時だけなので失敗しても大した問題は無いので,「自分はコンパイラより賢いんだ」という自信を持ってunwrapしても良いと思います.

実際まだまだコンパイラは人間より賢くないので,人間にとって自明に失敗しないことがわかる場合もRustはResultを使いたがることがよくあります.

また他の言語は常にunwrapしているようなもので,unwrapしてもちゃんと失敗した場所が通知されるので,他の言語を使うよりは安全だと思います.

追記(2019-02-28T16:29:30+09:00) rlsとdbgの使い方について

blackenedgoldさんから指摘を受けて記事を修正しました.

https://rust-jp.slack.com/archives/C1EV1LGHH/p1551335542000700?thread_ts=1551334851.000400&cid=C1EV1LGHH

追記(2019-03-01T16:39:37+09:00) nightlyをデフォルトにしないことを推奨することについて

nightlyだと安定化された機能をうっかりfeature無しで使ってしまう可能性があるのではhttps://rust-jp.slack.com/archives/C1EV1LGHH/p1551342507002100

という指摘がlo48576さん, tatsuya6502から入りました.

プロジェクトで使用するRustツールチェインのバージョンをチームで共有する - Qiitaを使って必要なときだけnightlyを使えることは知っていました.

しかしEmacsのrustic-modeがclippy要求などをnightly要求をしてきたので,nightlyデフォルトの方が面倒が無いのでnightlyをデフォルトした方が楽そうだなと判断してしまいました.

今ではrustic-modeもstableで動くようです.

stableでも動く人はそれでも良いでしょう.

追記(2019-03-08T21:12:48+09:00)

CIでstableでビルドとテストが通ることを確認していれば,nightlyの機能をうっかり使ってしまうことは無いのではと思えてきました.

しかしCIでstableを使う以上ローカル開発環境もやはりstableに揃えた方が良いですね…

nightlyはもはやrustic-modeにおいても不要になってきたので,もうnightlyにする必要はやはり無いのかもしれません.