読者です 読者をやめる 読者になる 読者になる

phpの名前空間をお手軽に変えるライブラリ書いてた

表題の通り php名前空間IDEみたいに置き換えられるコマンド書いてました。

実をいうと割と前の話で、社内の勉強会とかでは喋ってたんですが、ちゃんとパブリックで書いてなかったので書きます。あれからちょこちょこアップデートもしたし

これなに

主に php名前空間を置き換えるためのコマンドです。IntelliJ とか使ってる人にはおなじみの機能だと思いますが、それを php スクリプトで実現した感じです。

基本的に必要なオプションは以下の3つです。

  1. composer.jsonのパス
  2. 変更前の名前
  3. 変更後の名前

変更前後の名前を入力するのは当然だと思いますが、composer.json のパスを読み込ませるのは解析対象にするファイルを知るためです。

つまり composer.json

    "autoload": {
        "psr-4": {
            "YourName\\": "src"
        },
        "files": ["src/functions.php"]
    },
    "autoload-dev": {
        "psr-4": {
            "YourName\\": "tests"
        }
    },

と書いてあればその内容に従って src,tests,functions.php を解析対象にします。

なお、解析は nikic/PHP-Parser で行います。このライブラリについては qiita に書いたので興味あれば御覧ください。

正規表現とかではなくまともな方法で静的解析するので安定性はそれなりにある、と思います。

また、各ファイルの解析はマルチプロセスで並列実行されます。namae-space を作るにあたって、100%プレーンな php だけで動くように気をつけていたので速度面でちょっと不安がありましたが、並列実行にしたことによりまあまあ大丈夫なパフォーマンスになったような気がします。遅かったらすいません。php-cs-fixer よりはマシ、かも・・・。

ついでに言うとコードの内容を書き換えるだけではなくその名前のクラスを find する機能とかも乗っけています。そのあたりはリポジトリの readme を読んでみて頂ければ幸いです。

なんでつくったの

会社のリポジトリにグローバルな名前空間に作られたクラスが結構あってちょっとアレだったので、それを一息で殺すために作りました。もう2017年だし namespace ナシでクラス作る人はさすがにいないんですが、昔のコードにそういうのが残ってて殲滅したいなと思ってました。

先述の通り IntelliJ とかには名前空間を置き換える refactoring 機能がついていて、日頃から大変お世話になっているのですが、名前がグローバルだと置き換えられないんですよね。そういうわけで自分でスクリプト書きました。

便利オプション補足

一応 readme にも書いたんですが replace コマンドののオプションを一応ここにも載せます。以下は全て optional なので必要になったら参照して下さい。

-D:–dry_run

ドライランです。コードの置き換えは行わず diff だけ出力します。

-M:–max_process

静的解析の並列実行数を指定します。デフォルト10になっててちょっと強めかも。

-A:–additional_paths, -E:exclude_paths

先述の通り解析対象は composer.json を読み込んでよしなに出すんですが、お作法が違うディレクトリがあったりしたらそれらを強制的に加えたり外したりできます。グローバルな名前を置き換えたいとかいうニーズの場合このオプションが必要になるのではないでしょうか。

-R:–replace_dir

composer.json の内容だけでは新しい名前空間のファイルの置き場所が一意に決まらない場合があります。例えば先述した composer.json の内容で、YourName\Klass.php を作りたい場合、Klass.php は src 以下におけば良いのか tests 以下におけばいいのか厳密には判断できません。そうなった場合 namae-space はインタラクティブモードで「src と tests どっち使う?」と聞いてくるのですが、それをスキップしたい場合このオプションを使って直にファイルの置き場所を指定できます。

苦労話

gong023/namae-space はとにかく手軽に使えるものにしたくて、インストールgithub から phar を落とせば終わり、という形にしようかと思ってました。

しかし、phar だと proc_open が動かないんじゃないかという疑惑があり、このため phar による提供は一旦止めてます。

phar だと proc_open が動かないんじゃないかという疑惑

再現コードはこんな感じです。

gist.github.com

php をポチポチ gdb デバッグしてみると、なんかどうも この辺php phar://myphar… みたいな文字列が渡されていてそれを実行しようとするのでそりゃ動くわけないなって感じなんですが、これこういうもんなんでしょうか。

これだれか分かる人がいたら教えていただきたいです。

php-blt2でLTした

PHP BLT #2 - connpass でLTしてきたので資料貼っておきます

speakerdeck.com

普段ほとんどエンジニアの勉強会行かないしLTするのとか多分2年ぶりとかだったと思う。

自分の場合、ついこの前まで卓球ハウスにいたので技術の話する相手や機会には困らなかったけれど、卓球ハウスは解散したので今後もしかしたらこういう機会増えるのかなあとか思った。

内容について特に補足することはないけど、PHP拡張のマクロ情報少なくて面倒だなあという感じです。References に載せてあるリンクからの抜粋だけど、一応ここわかって助かったみたいなの書いときます。

  • PHP_CHECK_LIBRARY で必要な引数について
  1. The name of the library. In our case score will be transformed into -lscore when compiling. Example: cc -o conftest -g -O0 -Wl,-rpath,/usr/local/lib -L/usr/local/lib -lscore conftest.c
  2. The name of the function to try and find within our score library.
  3. The set of actions to take if the function is found. In our case, we are adding to the Makefile code to compile against our library and defining HAVE_SCORE which is used by the during compilation.
  4. The set of actions to take if the function is not found. In our case, we are throwing an error with a human readable error message.
  5. The set of extra library definitions. In our case, we are making sure the compiler knows where to find our shared object.
  • PHP_ADD_LIBRARY_WTH_PATH で必要な引数について
  1. The name of the library.
  2. The path to the library.
  3. The name of a variable to store library information. We will use this with PHP_SUBST.

詳しくはこちらで

rust-carbonっていうライブラリ作った

先月ようやく rust 1.0 が出たということで、いじってみるがてらライブラリをひとつ作った。

rust-carbon という名前で、rust で時間の扱いをやりやすくするものだ。

インターフェイスphpcarbon という有名なライブラリを真似たので、それが名前の由来となっている。以下使い方とか書いていく。

インストール

Cargo.toml にこれ書いて

[dependencies]
carbon = "0.1.*"

こう

extern crate carbon;

使い方

こんな感じ。現在時刻が 2015-01-15 01:30:30.500000000 だとして考えて欲しい。

// インポート
use carbon::*;

// 現在時刻は 2015-01-15 01:30:30.500000000

DateTime::now().start_of().second();
// => carbon::DateTime に 2015-01-15 01:30:30.000000000 が詰まって返る
DateTime::now().start_of().minute();
// => carbon::DateTime に 2015-01-15 01:30:00.000000000 が詰まって返る
DateTime::now().start_of().hour();
// => carbon::DateTime に 2015-01-15 01:00:00.000000000 が詰まって返る
DateTime::now().start_of().day();
// => carbon::DateTime に 2015-01-15 00:00:00.000000000 が詰まって返る
DateTime::now().start_of().month();
// => carbon::DateTime に 2015-01-01 00:00:00.000000000 が詰まって返る

DateTime::now().end_of().second();
// => carbon::DateTime に 2015-01-15 01:30:30.999999999 が詰まって返る
DateTime::now().end_of().minute();
// => carbon::DateTime に 2015-01-15 01:30:59.999999999 が詰まって返る
DateTime::now().end_of().hour();
// => carbon::DateTime に 2015-01-15 01:59:59.999999999 が詰まって返る
DateTime::now().end_of().day();
// => carbon::DateTime に 2015-01-15 23:59:59.999999999 が詰まって返る
DateTime::now().end_of().month();
// => carbon::DateTime に 2015-01-31 23:59:59.999999999 が詰まって返る

当然メソッドチェーンできる

DateTime::now().start_of().day().end_of().hour();
// => carbon::DateTime に 2015-01-15 00:59:59.999999999 が詰まって返る

単体テストなどで now の返り値を指定したい場合があると思う。rust-carbon では以下のように指定できる。

// 例えば現在時刻を 2015-01-01 00:00:00 にしたいとする
let tm = time::Tm {
  tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 1, tm_mon: 0, tm_year: 115, tm_wday: 4, tm_yday: 0, tm_isdst: 0, tm_utcoff: 0, tm_nsec: 0 };
};

let test_now = DateTime::create_from_tm(tm);
DateTime::set_test_now(test_now);
DateTime::now();
// => carbon::DateTime に 2015-01-01 00:00:00 が詰まって返る

また、rust-carbon は rust の準標準ライブラリである rust-time の薄いラッパーである。

そのため、rust-time の構造体及びそこに生えているメソッドに簡単にアクセスできる。to_string() とか細かい時間の調節とかはそっちのメソッドを使ってもらうことになる。

// 現在時刻は 2015-01-15 01:30:30.500000000

DateTime::now().tm.strftime("%Y-%m-%d %H:%M:%S").ok().unwrap().to_string();
// => "2015-01-15 01:30:30" という string が返る

use std::ops::{Add, Sub};

let hour = time::Duration::hours(1);
DateTime::now().tm.add(hour)
// rust-time の time::Tm 構造体に 2015-01-15 02:30:30.500000000 が詰まって返る

他詳細なことについては README を見てほしい。

補足

rust-carbon は習作で、冒頭にも書いたとおり、とりあえず rust 1.0 を触ってみたかったという動機から作られている。実用を目的としたわけではないので、実際に使おうとすると困る点がいくつかある。具体的には、まず UTC 以外の時間が扱えない(日本人だと絶望的)。あと time::Tm.add()/sub() を使った時に time::Tm が返ってしまうので、細かい時間調節をしようとすると小回りが効かない。

この辺りは気が向いた時とか実際に使い始めたとかされたら直していきたい。

また、rust で時間を扱うライブラリとしては、rust-chrono もある。rust-carbon はシンプルなのに対して、rust-chrono は複雑なことができる。 まぁ個人的には rust-chrono はインターフェイスがゴチャゴチャしていて気にくわないし、元々 rust-time にかなり任せられそうなので、あんな大仰で面倒くさそうなラッパーいるか?というのが正直な気持ちだが、もし rust で便利な時間操作系のライブラリを探しているとかいうのであれば、検討すると良いと思う。

rust 1.0 の雑感

rust は地味に nightly の頃にいじってみていたのだが 当時作ったライブラリは何にもいじってなくてもデイリーでビルドがぶっ壊れていてなかなか愉快だった。 公式ドキュメントの説明も難しかったし、まともな rust のコード読もうとすると rust 本体のコードしかないみたいな感じだった。

正直 1.0 も見送って 1.1 になるまで触るのやめようかなと思っていたのだが、今回の 1.0 でも触っていて先に挙げていたようなストレスはほとんどなくなっていた。ビルドもぶっ壊れなくなったし、今回の公式ドキュメント もだいぶ読みやすくなったと思う。

rust はモダンな言語仕様をふんだんに取り入れていて、本来持っているポテンシャルは高いと思う。細かいところはいちいち説明しきれないのでぜひ自分で見てくれという感じだが、結構堅牢でカッコイイ実装ができる。その上 ownership,borrow,lifetime の概念により省メモリに抑えることができる。結果として堅牢さと軽さが両立するのでかなりいい言語なのではという気がする。触っていてなかなか楽しかった。

ただ、go みたいな手軽さはほとんどない。サッと書いて動かして感動を得るというよりは、堅牢に設計した時の俺スゲー感とか、メモリチューニングを楽しんでいく言語だと思う。(go との比較ついでに書くと、並列処理書くのがまだ面倒なのが痛いが)

ちなみに、rust を今現在の仕事の現場で使いたいかというとそれはNOになる。あんまりぶっ壊れなくなったとはいえヘビーに触ったわけではないのでまだ怖いし、そもそも書く人が多いとはいえない言語なので現場で導入というのは考えられない。

言語本来のポテンシャルとしては他人と一緒に開発するのに向いているし、これから伸びていったら面白そう。

ctrl-aでDJした

一応備忘録として書いておこうと思う。割と前の話だけど、去る5/28に、秋葉原mograでctrl-aっていうイベントがあって、そこでDJした。

出るきっかけは本当にたまたまで、プライベートで仲良いkazzoneに飲み屋で出る?って言われたからだった気がする。酔っぱらってて出る出るって返事したら本当に出してもらえた。自分のような人見知りでもこういうことがあるのだから、人の縁というのは恐ろしい。

DJ自体は本当に初心者で、去年会社辞めて有給消化してるときにいじりはじめてそれからなんとなく練習してた程度だった。mograといえばアキバ文化の一端を担う聖地だし、自分じゃなくたってあそこにイベントで立ちたい人はいっぱいいると思う。出演前は嬉しいよりも畏れ多い気持ちで一杯だった。

いざイベントが始まると恐ろしいぐらいに人がたくさん来て盛り上がった。正直何が起きてるのかよくわからなかった。実際平日であれだけ人が集まったのは異常だと複数の人から聞いた。

自分は出番がオープニングだったので人は多くなかったのだけど、kazzoneがDJしてる時にVJとして前に立っていたので、場の盛り上がりを肌で感じることができた。好きな曲が流れた時に起こる歓声というか怒声みたいなものと、限界までボリュームが引き上げられたキック音とが混ざって起きる異常な振動は、日常生活ではなかなか感じることはできない。お客さんはちゃんと訓練されたクズで、色々なところで助けられた。本当にありがたかった。

ちなみにVJは本番の2日前にやってくれって言われて、一夜漬けで準備したとかそういう思い出もある。しかも準備してる時に仕事のほうでデカい障害出してしまって頭おかしくなりそうなぐらい脳汁出てた。キツかった。

DJの方は身内には良かったみたいなことは言ってもらえたけど、まぁ身内だし、初めてにしてはという枕詞が省略されていたので、客観的にみてお世辞にも上手くいってはいないと思う。一応ネットのどこかには当日のプレイを再現して上げたものもあるけど、とてもソーシャルアカウントとかで宣伝する気にはならない。選曲は本当に難しい。イベントの文脈、客層、自分の順番、他のDJの趣向、BPM、キー、その他諸々考えなければならないことがたくさんある。みんな軽々と回してるけどその難しさが身にしみた。

ctrl-aはおかげさまで次回もあるし、また出させてもらえそうなので次はもっとうまくやれるように頑張りたい。

goでテスト並列実行させたら楽な気がする

チームにある程度テストがかさばってくると、テストの実行時間が問題になる。せっかくテストを頑張って作っていても、実行時間が10分とか20分になってくるとどうしてもテストを動かすのが億劫になる。 せっかっく開発がノッてきたのに、テストのフィードバックがすぐ得られないと集中が途切れてしまう。手元なら実行範囲を狭めればいいだけだが、 CI 環境ではそうもいかないし、 そこでこけている原因を探るのに一回のトライエラーが10分間隔とかになると辛い。

まぁ、そういう建前は色々あるけど、テストのフィードバックが早く得られると困る人はいないと思う。

そういうわけでテストを並列実行しようという話が出てきて、php 界隈で並列実行を試みようとすると例えば以下のライブラリに行き当たる。

paratest は一番メジャーっぽいんだけど、php で書かれていて心配。php でマルチプロセスを行うのはかなりの黒魔術が必要なはずで、正直地雷踏みそうで近寄りたくない。 あとで見てみるとそんなことなさそう。昔読んだ php でマルチプロセスしてるコードが並列関係なく単純に出来が悪くて悪い印象持っていただけっぽい。

一方で parallel-phpunit は shell でなんか安心感がある。ただやっぱりそこは shell なのでテストの実行単位を分けていったりするのにもう少し自由度があったほうがいいのではという気がする。また少し離れたところでいうと、RRRSpec とかもある。これは確かにすごいのだが、こういう大艦巨砲が必要なわけではない。とにかく楽に学習コストとか下げてやりたい。

そんな感じの文脈で、ある程度費用対効果が良さそうな線を考えると、go で phpunit の並列実行するスクリプト書くのがいいんじゃないかなあという結論に至ってえいやで書いてみた。

package main

import (
    "fmt"
    "os/exec"
    "path/filepath"
    "runtime"
    "sync"
)

var (
    runner  = "./vendor/bin/phpunit"
    target  = "./tests/*"
    exclude = []string{"tests/fixture"}
    outputs = []byte{}
)

func main() {
    files, err := filepath.Glob(target)
    if err != nil {
        panic(err)
    }

    var wg sync.WaitGroup
    runtime.GOMAXPROCS(runtime.NumCPU())
    for _, file := range files {
        wg.Add(1)
        if contains(file, exclude) {
            wg.Done()
            continue
        }
        go func(file string) {
            out, err := exec.Command(runner, file).Output()
            if err != nil {
                wg.Done()
                fmt.Println("failed to execute: " + runner + " " + file)
                panic(err)
            }
            outputs = append(out)
            wg.Done()
        }(file)
    }
    wg.Wait()

    for _, output := range outputs {
        fmt.Printf("%c", output)
    }
}

func contains(value string, slice []string) bool {
    for _, s := range slice {
        if value == s {
            return true
        }
    }

    return false
}

このスクリプトでは、tests 以下のディレクトリごとに phpunit を並列実行している。今回はテストランナーを phpunit にしているけれど、var 以下を書き換えれば rspec でも karma でも testem でも何でも動かせる。

本当にとりあえずで書いたのでテストの実行単位が雑すぎるというのはあるけど、まぁそれに関しては拡張は難しくないのであまり気にしていない。 それよりも一番弱点になるのは CI 環境で go が必要になることだろうけど、別に致命的ではないと思う。go の書き方がなってない、とかそういう話は素直にすいませんといいたい。

とにかく楽に、費用対効果が高い形でテストを並列実行して高速化したいという話であれば go でスクリプト書くのはよい気がしてる。

PHPUnitを便利にするライブラリ幾つかつくった

年末年始+αの時間で PHPUnit をちょっと便利にするライブラリを幾つか作ったのでそれぞれ書いておく。

assert_chain

https://github.com/gong023/assert_chain

PHPUnit で一つの変数について詳しくアサーションをかけたい場合、以下のように書くことがあると思う。

<?php
$arr = [
    'intKey'    => 1,
    'stringKey' => 'foo',
    'boolKey'   => true,
];

$this->assertNotEmpty($arr);
$this->assertArrayHasKey('intKey', $arr);
$this->assertSame(1, $arr['intKey']);
$this->assertArrayHasKey('stringKey', $arr);
$this->assertSame('foo', $arr['stringKey']);
$this->assertArrayHasKey('boolKey', $arr);
$this->assertTrue($arr['boolKey']);

一体何度 $this->assert を何回書けばいいんだという話になるので、assert_chain を使って以下のように書けるようにした。以下は上記のコードと等価だ。

<?php

$this->assert()
  ->notEmpty($arr)
  ->arrayHasKey('intKey', $arr)
  ->same(1, $arr['intKey'])
  ->arrayHasKey('stringKey', $arr)
  ->same('foo', $arr['stringKey'])
  ->arrayHasKey('boolKey', $arr)
  ->true($arr['boolKey']);

また、PHPUnit の assert はほぼ全て $actual の値(つまりテスト対象の変数)を渡す部分があるが、centralizedAssert を使うことでテスト対象の変数が自動的にアサーションに渡るようになっている。

<?php

$arr = ['key' => 'value'];

$this->centralizedAssert($arr)
  ->notNull()
  ->notEmpty()
  ->notCount(0)
  ->count(1)
  ->arrayNotHasKey('no existing key')
  ->arrayHasKey('key')
  ->notContains('no existing value')
  ->contains('value')
  ->equals(['key' => 'value']);

アサーションPHPUnit_Framework_Assert にプロキシしてるだけなので、普段 PHPUnit を使っている人は違和感なく使えるはず。当然ロジックも変わらない。

詳しいことは README を見て欲しい。

assertEqualsassertSame を使って一度に雑にテストするのもいいが、このように複数アサーションを重ねて段階的にテストしていくとテストが失敗した時に原因がわかりやすい。

Ayaml

https://github.com/gong023/Ayaml

yml による fixture ライブラリ。yml ファイルから php array を生成する。

# user.yaml
valid_user:
  id: 1
  name: Taro
  created: 2014-01

上記ファイルから以下のように array を作る。

<?php
Ayaml::file('user')->schema('valid_user')->dump();
=> ['id' => 1, 'name' => 'Taro', 'created' => '2014-01'];

with メソッドを使って値の上書きもできる。

<?php
Ayaml::file('user')->schema('valid_user')->with(['id' => 2, 'name' => 'John'])->dump();
=> ['id' => 2, 'name' => 'John', 'created' => '2014-01'];

以下のようにシーケンシャルな配列を作ることもできる。

<?php
$validUser = Ayaml::file('user')->schema('valid_user');

Ayaml::seq($validUser)->range('id', 10, 12)->byOne()->dump();
=>
[
  ['id' => 10, 'name' => 'Taro', 'created' => '2014-01'],
  ['id' => 11, 'name' => 'Taro', 'created' => '2014-01'],
  ['id' => 12, 'name' => 'Taro', 'created' => '2014-01'],
];

Ayaml::seq($validUser)->between('created', '2014-01', '2014-03')->byMonth()->dump();
=>
[
  ['id' => 1, 'name' => 'Taro', 'created' => '2014-01'],
  ['id' => 1, 'name' => 'Taro', 'created' => '2014-02'],
  ['id' => 1, 'name' => 'Taro', 'created' => '2014-03'],
];

詳しいことは README を見て欲しい。

Ayaml の特徴は、テストデータそのものを DB データとして作るのではなく、単純に array を生成する点。

php の fixture ライブラリとして有名なものとして、DBUnit, alice, php-factory-girl がある。これらのライブラリは、Ayaml とは違い DB にデータを入れるところまで責任を持ってくれる。

もちろんそれはそれで良いのだが、一方で DB 周りを扱うライブラリは馴染みのあるものを使いたいという気持ちはないだろうか。少なくとも自分にはある。上記のライブラリは doctrine を使うことになるので、テストのためにわざわざその DSL を覚える羽目になる(DBUnit はそうとも限らないが、あれはあれでよく我慢して使えるなと思ってる)。正直それは馬鹿げているし、あーシャーディングされてる場合どうすんだっけとか考えだすとテストデータひとつ作るのに膨大な時間を費やす羽目になる。

Ayaml は DB にデータを入れるという責任を放棄し、手前で好きに array 作って手前のライブラリに流し込めというスタンスにすることで自由度を上げている。php には active_record のような支配的なライブラリがないので、こういうアプローチもありだと考えた。

ただ一方で、Ayaml を実装する際自分に課した制約として「doctrine の DSL を覚えるより短い時間で実装すること」というのがあり(それ以上かかるなら既存ライブラリ使ったほうが早いから)、実際 ver0.1.0 までは 1h 弱ぐらいの時間で実装したので、特にシーケンシャル array を作る所なんかはパワー不足な面が否めない。 Ayaml 以上に仕事してくれる DSL が欲しいとか、プロダクトで doctrine を使っているとかいう事情があれば、alice を検討するのがよいと思う。

PHPUnityo

https://github.com/gong023/phpunityo

PHPUnit の成功時 or 失敗時に Yo してくれる。travis でも使える。

まぁ自分は CI の結果は普通にメールで見てるのでぶっちゃけそんなに必要ないのだが、Yo で送られてくるとめでたい。それだけ。

設定は Yo の API token を環境変数に突っ込んだ上で phpunit.xml.dist に以下の情報を足せばいい。

<listeners>
  <listener class="PHPUnit_Yo" file="vendor/gong023/src/PHPUnit_Yo.php">
    <arguments>
      <string name="sendUser">Your Name</string>
      <string name="onSuccess">false</string>
      <string name="onFailure">true</string>
    </arguments>
  </listener>
</listeners>

詳しくは README を見て欲しい。

underscore-rust作ってみた。あとrust書いてみた動機とか

underscore-rust

underscore-rust 作った。名前から分かる通り、underscore.js の機能を rust にしたもの。

ライブラリ自体のドキュメントは上記に入れたつもりなので、必要であれば参照して欲しい。ポイントとしてはオリジナルの underscore.js のように _ の構造体みたいなものを用意するのではなく、標準の struct(Vec, TreeMap, HashMap)にそのまま underscore 系の関数を追加した点。use すれば普通の API と同じ感覚で使うことができる。

作ったみたが、インターフェイスについては結構後悔している部分が多い。例えば HashMap の拡張は

 fn invert(self) -> HashMap<V, K>;

とかにしてしまったけどテストしづらかったとかいう理由で ownership 取ってしまったので(ownership については後述)そこに関しては

 fn invert(&'a self) -> HashMap<&'a V, &'a K>;

とかにすべきだったかなと思っている。まぁ、同じ型のものが返るので不都合はなさそうだしこっち方が扱いやすくていいだろと考えたのでこういうインターフェイスにしたのだが、オーバーヘッドが大きくなってしまうのは事実だ。所詮初学者が作ったものなので見る人から見ればおかしい部分が多分に含まれているはずで、その辺りの指摘は甘んじて受けていきたい。

作った動機

特に underscore-rust を作ってみたい実用的な用事があったわけではなく、単純に rust を書いてみたくて、教材として便利だったので作ってみた。underscore系のライブラリは、一体何番煎じか知らないが、とりあえず新しい言語を覚える際には良い教材だと思う。難易度は低いし、割と有益なものを作れる。javascript を引き合いに両者の違いを感じることができる点も良い。

自分は普段 LL しか使わない完全なゆとりエンジニアだが、rust はそういった層により低レイヤーを学ばせる用途に向いている。LL とかそれより上流のコミュニケーション一式が主戦場であっても、パフォーマンスチューニングだとかインフラの冗長化を見積もったりする段階で低レイヤーにまで手を伸ばさなければならない場合は多い。ライブラリの選定で知識が必要な時もある。あるいはもっと単純に、低いレイヤーのことを知るのは物事の道理を学ぶにあたって有益だと思う。 最近はインフラ層の技術がより LL がメインの人たちに近づいてきた印象があり、自分もそういうものと付き合っていかなければないので、この辺りで一度レイヤーが低いっぽい言語に触れておきたかった。

rust に手を出してみたきっかけはこういった気持ちがあったからだけど、これより前は同じような理由で go を触ってみたりしていた。ただ、GC が支配的な世界観でなんとなく違う気がして離れてしまった。rust のメモリ管理は基本人間が頑張るのでそういうのに惹かれたんだと思う。

こう書くと C とか C++ とかやればいいという完全に真当なことを言われそうなのだが、何か一つ知らない言語を選べといわれたらそれなりに新しいものを選びたくなる程度にはミーハーなので正直やる気がおきなかった。あとかなり前に読んだ「ふつうのプログラマのための haskwell〜」とか「7つの言語〜」とかで関数型言語は確かに綺麗だなあという気持ちがチラついたのもある。純粋な興味だけで言ったら関数型言語をガッツリやってみたかったのだが、いくらかでも目の前の課題とか明日のメシと地続きにする知識を得ようとしたら、関数型っぽい風味のある rust に手を出すというのは一つの着地点としてアリだった。

rust のちょっといい話

※この項は特に間違えている可能性が多く含まれるので気をつけて下さい

ポジショントークだけだとさすがにアレなので、具体的に rust のちょっといい話を考えてみた。例えば C でこんなコードを書いたとする。

struct hoge { int num; };

int* pick_num(struct hoge *h)
{
    int *copy = &h->num;
    free(&h);
    return copy;
}

int main(void)
{
    struct hoge *x;
    x = (struct hoge *) malloc (sizeof(struct hoge));
    x->num = 5;

    int *y = pick_num(x);

    printf("%i\n", x->num);
    printf("%i\n", *y);

    return 0;
}

上記のコードだと y は free された値を参照してしまうダンジリングポインタとなる。これはコンパイルは通るが、実行すると落ちる。このコードを rust に置き換えてみる。

struct Hoge { num: int }

fn pick_num(x: Box<Hoge>) -> int {
    x.num
} // rust の場合、ブロックを抜けるときに常にメモリ解放されるので RAII は意識しなくていい

fn main() {
    let x = box Hoge { num: 5i };

    let y = pick_num(x);

    println!("{}", x.num);
    println!("{}", y);
}

これをコンパイルしようとすると、以下のようにエラーを吐いてそもそもコンパイルできない。

hoge.rs:13:20: 13:25 error: use of moved value: `x.num`
hoge.rs:13     println!("{}", x.num);
                              ^~~~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
hoge.rs:13:5: 13:27 note: expansion site
hoge.rs:11:22: 11:23 note: `x` moved here because it has type `Box<Hoge>`, which is non-copyable (perhaps you meant to use clone()?)
hoge.rs:11     let y = pick_num(x);
                                ^
error: aborting due to previous error

例に挙げた C のコードが阿呆すぎるような気もするが、とにかく、このようにコンパイルの段階で危険なポインタを教えてくれるのが rust の利点の一つだ。

これを支えているのは ownership,borrow,lifetime という概念で、以下に非常に雑な説明をすると

  • 全ての変数に ownership が設けられる
    • ownership を持つものだけがその変数へのアクセス権を持つ
  • 変数の ownership は borrow することができる
    • borrower は ownership を取らずに変数を使用できる
    • ただし、borrower はメモリ解放や値の書き込みを行うことができない
  • borrow には lifetime が設けられる
    • コンパイラが自動で付加してくれるので普段はあまり意識しなくてよいが、参照を返す際などは明示的に lifetime を宣言して返したりする

とかになるが、ownership,borrow,lifetime は変数が mutable だった場合の挙動とかが少しずつ違ったりして一口に説明するのが難しいので、できれば公式ドキュメントの ここ とか ここ を読んでみて欲しい。

今回の例だと、let x の ownership を pick_num 関数に移譲してしまっている。そのため、再度 x にアクセスしようとしてもできない。こういうコードの場合、pick_num の引数は単に borrow してくるのが普通である(はず)。

参考になったもの

rust を学ぶ上で役に立ったものを挙げてみる。

  • http://doc.rust-lang.org/
    • やはり公式ドキュメント。基礎がしっかりある人なら1日あれば読めるかもしれない。自分はかなり時間がかかった。
  • http://rustbyexample.com/
    • tour of go みたいなもの。tour of go ほど体系立ってるわけではないが、公式ドキュメントからの概念的な説明と並行しながら見るといいと思う。
  • http://doc.rust-lang.org/nightly/std/https://github.com/rust-lang/rust
    • 実際に rust で何か作っていこうとなったとき、rust way がわからない。そんな時に組み込みAPIの実装を読めて強力。一般に流通している rust ライブラリよりも綺麗に設計されている。
  • http://rustforrubyists.com/
    • rubyist 向けの rust 導入記事。今思うと大したこと書いてない気がするのだが、導入には良さそう。何より自分のようなゆとりでも門戸が開かれている事実が嬉しかった。