rustでtelnetチャットを書いた

ソケットプログラミングの練習でよくあるアレをrustで書いてみた。

https://github.com/gifnksm/telnet-chat

クライアントプログラム書くのがダルかったので、telnet を使う仕様にした。 \ から始まるコマンドを打つことで、参加者一覧を出したりできる。 練習のため、通信関係の処理はすべて標準ライブラリのものを利用している。 雑に設計して雑に書いたので無駄なスレッド生成とかいっぱいしているけどそれはご愛敬。 もうちょっと改善したいけど連休も終わるのでひとまず投稿した次第です。

  • Rust だからといって、並列プログラミングが超お手軽に書けるわけではない。異常系処理をきちんと作り込んむためにはスレッド間のやりとりなどはきちんとした設計が必要。
  • とはいえCで書くよりは断然楽。enum とパターンマッチ最高という思いを強くした。型定義だけでプロトコルが定義できるのは分かりやすい。C APIを叩く準備などの細かい処理に意識をとらわれず、本質的な論理に集中できるというのもやはり良い。move semantics や borrow checker のおかげでスレッド間のデータの排他についてそれほど気を遣わなくて良いのも大きい。
  • 一番ハマったのは、socket 全然関係無く docopt の使い方。 let arg: Args = Args::docopt()...: Args を書き忘れたばっかりに、decode 処理で実行時エラーになっていた。どうも型指定省略すると args の型が () になるっぽい。謎。
  • socket とか channel が Rust のライブラリで抽象化されているため、C で書いたときのようにどんな場合にどんなエラーが返ってくるのか man で調べつつ書く、というのができなくてエラー処理をどうすれば良いのか (リカバリ可否の判断とか) が判断できない局面があった。クロスプラットフォーム対応のために難しいのかもしれないけど、もう少しドキュメントに詳細なエラー原因とかがあると良いと思った。

ちゃんとしたサーバアプリケーションを書くならば、mio とか使うのがよさそう。

gunosyから元記事に自動的に飛ぶユーザースクリプト

Gunosyから届くメールのリンクのうちいくつかが元記事ではなくGunosy内に転載された記事に飛ぶようになっていて()腹が立ったので書いたので広報する。

ページ内から「元記事を探す」のリンクを探してきて、location.hrefに設定するだけです。

multirust で今日から始める快適 rust 生活

Rust 1.0 リリースおめでとうございます!!!!

Rust 1.0 がリリースされました!!!!めでたい!!!!!!

このリリースは非常に大きな一歩です。これまではコンパイラのバージョンが上がる度にコードを頑張って修正する必要がありましたが、今後はstablerustc を使って作ったコードについては、将来にわたってもコンパイル可能で動作することが保証されます。 (機能の deprecation などはありますので、絶対的なものではありませんが、破壊的変更の頻度・度合いはこれまでよりもだいぶ控えめになるはずで、バージョンアップへの追従を諦めてしまうこと、いわゆる "Rust 疲れ" 現象は減ることが期待できます)

現時点での Rust を取り巻く環境の問題

言語の安定化により、サードパーティーのライブラリは今後どんどん充実していくと予想されます。Rust の将来はバラ色になるに違いないと筆者は信じていますが、1.0がリリースされたばかりの現時点ではまだまだ理想の状態には到達していないのが現実です。例えば、本日実施された Rust 1.0 Release記念祝賀LT会 では、多くの方が以下について困っていると訴えていました。

  • 使いたいライブラリが unstable な機能を使っているため、stable なコンパイラではコンパイルできない
  • 使いたいライブラリがしばらく更新されておらず、最新の rustc + cargo ではコンパイルできない

これらを回避するためには、nightly/beta バージョンのコンパイラと stable バージョンのコンパイラを、プロジェクトごとに切り替えて利用する必要があります。もしかしたら、特定の日付の nightly build を利用する必要が出てきてしまうかもしれません。非常にめんどくさい作業ですね。

今すぐに Rust を活用したい、そんなあなたのためのツールmultirust です。

multirust のススメ

multirust は、一言で言ってしまえば rubyrvmpythonpyenv のようなもので、複数バージョン (リリースチャンネル) の Rust ツールチェイン (rustc & cargo) を簡単に切り替えて使えるようにするものです。

Rust の開発環境を整える方法としては、公式サイトに記載されている rustup.sh を利用する方法 が有名ですが、multirust を使う方法をオススメします。 (Rust のコアチームの一員の Steve Klabnik さんも 非Windows環境のユーザーについては multirust の利用を推奨しています)

multirust のインストール

以下コマンドをコピペするだけで multirust のインストールは可能です (2015/5/16 現在。実際にインストールする際は README を読んで最新の手順を確認してください)。

curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh

上記を実行すると、multirust コマンドと nightly版の rustccargo がインストールされます (既に rustc などがインストールされていると、衝突するかも?)。

multirust の使い方

multirust のインストール直後の状態では、 rustc/cargo コマンドを実行すると、nightly 版の各コマンドが実行されます。

あるプロジェクトについて、stable 版の rustc/cargo を利用したい場合、以下を実行します。

cd <path to project>
multirust override stable

こうすることで、当該ディレクトリのパスとコンパイラバージョンの対応関係が記録され、当該ディレクトリで実行する rustc/cargo については、 stable 版が利用されるようになります (beta 版が使いたい場合は、 multirust override beta とする)。

また、特定の日付の nightly 版を利用することもできます。

cd <path to project>
multirust oberride nightly-2015-05-16

nightly 版のツールチェインについては日々の更新に追従する必要があるかもしれません。デフォルトでは multirust をインストールした時点で最新の nightly が利用されますが、最新の nightly を利用するようにするためには、以下を実行して保存されているツールチェインを更新します。

multirust update nightly

毎日の nightly 更新作業が便利になりますね!

なお、チャンネル名を指定せず multirust update する場合、nightly, beta, release の3チャンネル全てのコンパイラが更新されるそうです。

まとめ

multirust 便利!!!

詳しい話は multirust の README によくまとまっているので読みましょう。

Rust nightly の x86_64, i686 両対応版の PKGBUILD

タイトルの通りのブツです。これを参考にして rust-bin-nightly を改造した感じです。 パッケージのビルド時間が短くなって大変ハッピー。

# Maintainer: NAKASHIMA, Makoto <makoto.nksm@gmail.com>
pkgname=rust-nightly-bin
pkgver=2015.01.25
pkgrel=1
arch=('i686' 'x86_64')
pkgdesc='A safe, concurrent, practical language from Mozilla.'
url='http://www.rust-lang.org/'
provides=('rust' 'cargo')
conflicts=('rust' 'rust-git' 'rust-nightly' 'cargo-nightly-bin')
depends=('shared-mime-info')
license=('MIT' 'Apache')
source=('https://raw.githubusercontent.com/rust-lang/rust/master/src/etc/gedit/share/mime/packages/rust.xml'
        'rust-nightly.conf')
sha256sums=('a2a6609d6a89dd57f1bf874da222316c2b3c8ffcfe5b5ae87f8489096744446d'
            'fc2ed32e9841d2080803b117a773aa6606fc74391fec76fbadd2e952ca1fc256')
install=rust.install
options=(staticlibs !strip)

_target_os=unknown-linux-gnu
_nightly_x86_64="https://static.rust-lang.org/dist/rust-nightly-x86_64-$_target_os.tar.gz"
_nightly_i686="https://static.rust-lang.org/dist/rust-nightly-i686-$_target_os.tar.gz"

pkgver() {
  date --utc -d"$(curl -I $_nightly_x86_64 | sed -n 's/^Last-Modified: *//p')" +"%Y.%m.%d"
}

package() {
  local INSTALL_DIR=/usr/local
  local MAN_DIR=/usr/local/man

  curl -O $_nightly_x86_64
  curl -O $_nightly_i686
  tar xf rust-nightly-x86_64-$_target_os.tar.gz
  tar xf rust-nightly-i686-$_target_os.tar.gz

  cp -r rust-nightly-i686-$_target_os/lib/rustlib/i686-$_target_os \
     rust-nightly-x86_64-$_target_os/lib/rustlib

  cd rust-nightly-x86_64-$_target_os
  find lib/rustlib/i686-$_target_os/lib -type f | sed 's/^/file:/' >> manifest-rustc.in

  # Rust, Cargo and Documentation.
  ./install.sh --prefix=${pkgdir}${INSTALL_DIR} --mandir=${pkgdir}${MAN_DIR}

  # Establish .rs MIME type association.
  mkdir -p "${pkgdir}/usr/share/mime/packages"
  install -m 644 "${srcdir}/rust.xml" "${pkgdir}/usr/share/mime/packages/rust.xml"

  # Dynamic linker configuration (no LD_LIBRARY_PATH required).
  mkdir -p "${pkgdir}/etc/ld.so.conf.d"
  install -m 644 "${srcdir}/rust-nightly.conf" "${pkgdir}/etc/ld.so.conf.d/rust-nightly.conf"

  # Remove manifest file referencing $pkgdir.
  rm -f ${pkgdir}/usr/local/lib/rustlib/{manifest-rustc,manifest-cargo,components,rust-installer-version}
}

Rustでコンパイル時処理に挑戦する

この記事は Rust Language Advent Calendar 2014 の21日目の記事です。 20日目はRyuseiさんの「Go, Rust, Haxeによる正規表現エンジン実装の読み比べ - M59のブログ」、22日目はwoxtuさん担当です。

Rustのコンパイル時処理

システムプログラム界隈でコンパイル時処理というと、C++constexpr やDのCTFE (Compile Time Function Execution; コンパイル時関数実行) が有名です。Rustでもこれらと類似の機能をサポートしようという話は何度も持ち上がっていますが、残念ながら現時点ではサポートする予定はないようです (どこかでpost1.0の機能として上がっているのを見た気がしますが、URL失念。)

Rust 1.0 にCTFEはありませんが、コンパイル時処理を行うことができる "compiler plugin" という(実験的)機能が存在します。"compiler plugin" 機能は、C++/Dのコンパイル時処理とはだいぶ異なった使い勝手になっています。詳しくは The Rust Compiler Plugins Guide を参照してください。

デモ

さて、そんなRustのコンパイル時処理ですが、何ができるのでしょうか。簡単なプログラムを作ってみました。

https://github.com/gifnksm/cttw-rs

使い方を説明します。

まず、適当なディレクトリで cargo new を実行します。

$ cargo new --bin hello
$ cd hello

Cargo.tomlに以下を書き足します。

[dependencies.cttw]
git = "https://github.com/gifnksm/cttw-rs"

また、自動的に生成されているsrc/main.rsの先頭に以下を追加します。

#![feature(phase)]
#[phase(plugin)] extern crate cttw;

これで準備は完了です。コンパイルしましょう。

$ cargo build
   Compiling pkg-config v0.1.2
   Compiling gcc v0.1.2
   Compiling url v0.2.6
   Compiling curl-sys v0.1.2
   Compiling libz-sys v0.1.0
   Compiling openssl-sys v0.2.6
   Compiling time v0.1.3
   Compiling curl v0.1.2
   Compiling rust-crypto v0.2.2 (https://github.com/DaGenix/rust-crypto#2a2220b8)
   Compiling cttw v0.0.1 (https://github.com/gifnksm/cttw-rs#41848736)
   Compiling hello v0.0.1 (file:///home/nksm/tmp/tmp/hello)
input your consumer key:

なにやら見慣れないプロンプトが登場します。ここでは、Twitterのアプリケーション向けconsumer key を入力するよう指示されています。Twitterのサイトで適当なアプリケーションを作成し、割り振られたconsumer keyを入力してください。

input your consumer secret:

続けてconsumer secretを要求されるので、入力してください。

open the following url:
        https://api.twitter.com/oauth/authorize?oauth_token=XXXXX
input pin:

ここまで来たら後一歩。表示されたURLにアクセスし、Twitterアカウントでログインおよび認証後、表示されるPINコードを入力してください。 その後、何事も無かったかのようにコンパイルが終わります。Twitterを見てみると、、、

コンパイルするだけでツイートされている!!!!!!!!!

なお、2度目のコンパイルからは認証用情報の入力は不要です (カレントディレクトリに情報を保存します)。

種明かし

compiler plugin は、コンパイル時にユーザが定義した関数を呼び出すことでrustcの機能を拡張します。これらのユーザ定義関数は、動的ライブラリ内で定義され、rustcはコンパイル時にライブラリをリンクします。リンクされる側のライブラリは普通のRustコードですので、任意の処理を書くことができます。

今回作ったライブラリでは、以下のように、プラグインを登録する処理を行う関数内にツイートする処理をぶち込むことで簡単にコンパイル時処理を実現してしまいました。

#[plugin_registrar]
pub fn plugin_registrar(_reg: &mut Registry) {
    let conf = match config::read() {
        Some(c) => c,
        None => {
            let consumer_key    = console_input("input your consumer key:");
            let consumer_secret = console_input("input your consumer secret:");
            let consumer = Token::new(consumer_key, consumer_secret);

            let request = twitter::get_request_token(&consumer);
            println!("open the following url:");
            println!("\t{}", twitter::get_authorize_url(&request));
            let pin = console_input("input pin:");
            let access = twitter::get_access_token(&consumer, &request, pin.as_slice());

            let c = Config {
                consumer_key: consumer.key.to_string(),
                consumer_secret: consumer.secret.to_string(),
                access_key: access.key.to_string(),
                access_secret: access.secret.to_string()
            };
            config::write(&c);
            c
        }
    };

    let consumer = Token::new(conf.consumer_key, conf.consumer_secret);
    let access = Token::new(conf.access_key, conf.access_secret);
    twitter::tweet(&consumer, &access, "あなたとRust, 今すぐコンパイル");
}

まじめなはなし

今回作ったのはしょぼいネタライブラリでしたが、"compiler plugin"機能を活用した便利ライブラリももちろん開発されています。

少し検索しただけでこれだけのライブラリが出てきたので、他にもいろいろあるのでしょう。 外部の静的解析ツールに頼ること無く独自のlintを追加できるのは、compiler pluginの強みだと思います。

かなしいはなし

本記事で紹介してきた compiler plugin 機能ですが、利用するために "#![feature(...)]" が必要な実験的機能となっています。rustcは今後リリースチャンネルモデルに移行するようですが、実験的機能はbetaやreleaseのチャンネルでは無効化されてしまうため、利用するためにはnightlyチャンネルを利用する必要があります。一般のrustユーザまでcompiler pluginが届くのは、まだまだ先になりそうです。。。

謝辞

今回、compiler pluginを作る箇所は簡単に作れたのですが、Twitterに投稿する処理の作成で非常に苦労しました。明日の記事担当のwoxtuさんのdlang - 市民、認証は義務です - Qiitaが大変参考になりました。ありがとうございます。

nicovideo Thumbinfo popup 更新 (現カテゴリタグに追従)

nicovide Thumbinfo popup を更新しました。

カテゴリタグデータを最新化しました。 (316さん、ご指摘ありがとうございます)

nicovideo Thumbinfo popup の配付サイトを移動 & @grant 対応

超久々 Greasemonkey 更新情報です。

nicovideo Thumbinfo popup の配付サイトとして利用していた userscripts.org が死亡したらしいので、 新しい配付サイトとして Greasy Fork へと移動しました。

スクリプトの自動更新など行っている方は、上記サイトから再度スクリプトをダウンロードすると良いと思います。

ちなみに Greasemonkey 2.0 (と、Scriptish の最近のバージョン) で必要になったっぽい、 @grant 指定についても対応しています。 実は 5/2 にこっそりと更新していたのですが、このブログ上では告知していなかったので。。。

ついでに、 GitHubリポジトリを作成しました。

今後の方向性ですが、このスクリプトFirefox の独自構文を多様した作りになっているので、 ES6 や 7 の標準に従った作りにしていきたいな、と考えています。 Google Chrome 等との互換性をとるのも大変な作りになっていますし、Firefox 独自機能はいつ削除されるか分からないので。。。 あとは、もう少し全体的に見通しの良い作りにしたいですね。 めっきり静的型付け信者になってしまったので、 TypeScript への移行も選択肢の内かも。