エンジニアですよ!

頑張れ俺くん、巨匠と呼ばれるその日まで

nginx + ngx_mruby を homebrew でインストールしたかった

結論からいうとできたんだけど、ちょっとハマった。

ちょっと、本当にちょっとだけ ngx_mruby 試してみたくて、簡単に入ればいいなーと思って、探して↓を見て喜々として試したわけだけど

ngx_mrubyがHomebrewで超簡単にインストールできるようになった - 人間とウェブの未来

既に issue を上げてる人がいたけど、今 brew install nginx-full --with-mruby-module としても一発でインストールできない。

こんな感じで。どう考えても書き込み権限あるディレクトリに書き込めなくて死ぬ。

brew install nginx-full --with-mruby-module
==> Installing nginx-full from homebrew/nginx
==> Downloading https://nginx.org/download/nginx-1.12.1.tar.gz
######################################################################## 100.0%
==> git init
Last 15 lines from /Users/xxx/Library/Logs/Homebrew/nginx-full/01.git:
2017-08-13 15:21:22 +0900
git
init
error: could not lock config file /usr/local/Cellar/mruby-nginx-module/1.20.0/share/mruby-nginx-module/.git/config: Operation not permitted
fatal: could not set 'core.repositoryformatversion' to '0'

原因は --verbose するとわかるけど homebrew は sandbox-exec を使って sandbox 下でビルドするようになってて、それを想定していないこの formula がおかしくなってしまった様子。

homebrew の 1.0.0 が出たのが2016年9月で、1.0.0 から sandbox を使うようになったから恐らくそれからインストールできなくなってしまったんだろう。(きっと)

mruby の install スクリプトgit 管理されていることを想定されているとかコメントに書かれていたりするし homebrew のことも mruby のことも何も知らないので自分がちょっと調べて直せるってことはなさそうだったので、とりあえず今回は --no-sandbox オプションで回避した。

これでイケると思ったんだけど、まだうまくいかず。

ld でエラーが出てどうも openssl あたりのリンクがうまくいかない。

これもどうすればうまくいくのかわからなかったので、 forkしてオプションを追加してbrew edit してしまうというイケてない方法で回避した。

ちゃんと読んでないけど LIBS で -lcrypto は普通に指定されているような気がするので、これは自分の環境が悪いのかもしれないし、ちょっと今試したいだけで特に真面目にそこを調べるほど時間掛けたくはないのでとりあえず無視…

また使うことがあれば調べる。

あと今回は自宅の mac 上で使いたかったから brew で入れたかったんだけど普通に ngx_mruby 試すだけなら docker とかあるしそっち使うほうがいいだろうね。(というか mac 上でとはいえ docker でできない理由はなかった)

docker で Run `bundle install` to install missing gems になっちゃう件

Docker で Gemfile に変更があったときだけ bundle install を実行するために、 Gemfile と Gemfile.lock を先に ADD しておいて bundle install を先に実行しておくのはもはや常識だと思う。

ちょいちょい例にこういうのがある。(ここでは Dockerfile とアプリケーションが同じリポジトリディレクトリ)にあるとして、そのディレクトリで docker build してるとする)

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock

....

ADD . /app

どういうのかというと、 bundle install を先に実行してキャッシュを作るときのディレクトリと、実際にアプリが動くディレクトリを別にしているという例。

これは通常問題ないんだけど、bundle install の際につけるオプションによっては問題になる。

例えば bundle install –without development test とかする場合。

/tmp 以下には install 時に .bundle/config が作られてそこに BUNDLE_WITHOUT の情報が書き込まれるので、特定の group が除外されていることがちゃんと bundler に伝わる。

しかし、↑でいうと /app 以下で bundle exec とかするときにはその情報は伝わらないので、without オプションを指定して意図的に除外した group の gem がインストールされていないと、タイトルに書いたようなエラーが出てしまう。

解決策としては普通にアプリを動かすディレクトリで bundle install をやっておけばいい ( bundle config --global without development:test[:…] とする手もある )

Rust でマルチスレッドプログラミングのメモ

Rust でのマルチスレッドの勉強のためにぷよぷよっぽいゲームを書いているので、学んだことを書いておく。

Rust ではデータ競合は起こせない

Rust はデータ競合 ( data races ) がないことを保証している。( data races であって race conditions ではない。 Races - 参照

所有権のシステムによって同一のメモリを複数箇所から同時に書き換えることができないようになっており、これをコンパイル時にチェックすることで安全性を保証している。

これに違反している場合はコンパイルが通らない。

これはマルチスレッドでも適用されるので、安心してコードを書くことができるが、その分最初はコンパイルが通らなくて悩むことも多かった。

スレッドを使ってみる

単純な例

スレッドは標準ライブラリの thread::spawn を使う。

以下はスレッドを生成して1秒後に hello と出力する。

use std::thread;
use std::time::Duration;
fn main() {
    let t = thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        println!("{}", "hello");
    });
    let _ = t.join();
}

https://is.gd/tnYGhZ

この場合はなんの問題もない。見た目通りの動作をしていると思う。

spawn に無名関数 (|| {..} は引数を取らないクロージャね) を渡していて、それが別スレッドで動く。

上のクロージャは外部の変数を使っていなかったが、変数を使うようにしてみる。

変数を出力するようにしてみる。

fn main() {
    let name = "John";
    let t = thread::spawn(|| {
        thread::sleep(Duration::from_secs(1));
        println!("hello {}", name);
    });
    let _ = t.join();
}

https://is.gd/deGnRU

これはエラーになる。

error[E0373]: closure may outlive the current function, but it borrows `name`, which is owned by the current function
 --> <anon>:5:27
  |
5 |     let t = thread::spawn(|| {
  |                           ^^ may outlive borrowed value `name`
6 |         thread::sleep(Duration::from_secs(1));
7 |         println!("hello {}", name);
  |                              ---- `name` is borrowed here
  |
help: to force the closure to take ownership of `name` (and any other referenced variables), use the `move` keyword, as shown:
  |     let t = thread::spawn(move || {

spawn の signature を見てみると、引数は F: FnOnce() -> T, F: Send + 'static とある。

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static

これはクロージャ'static な生存期間を持つことを意味している。

'static な生存期間を持つクロージャが、生存期間が関数内だけの name を借りようとしているのでクロージャのほうが長生きしてしまうよとエラーになる。

これはエラーで言われている通り、 move キーワードを使ってクロージャに所有権を渡してしまえば良く、ほとんどのケースでは move を使うことになる。

ただ、move キーワードを使うということは当然所有権はクロージャ内に移ってしまうので、そのスレッド以外でも同じデータを使いたい場合には困ってしまう。

位置情報を持てる Enemy 構造体を作り、スレッド内で敵の位置を使ってなんらかの処理をしつつ、メインのスレッドで描画する(この場合は位置を表示するだけ)ことを考えてみる。

こうすると、enemy の所有権が移った後に使おうとしているのでエラーになる

...

struct Enemy {
    x: i32,
    y: i32,
}

fn do_something(enemy: &Enemy) {
    println!("{}", enemy.x + enemy.y); 
}

fn main() {
    let enemy = Enemy { x: 0, y: 0};
    let _ = thread::spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(1));
            do_something(&enemy);
        }
    });
    
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/XJhXOZ

rustc 1.17.0 (56124baa9 2017-04-24)
error[E0382]: use of moved value: `enemy.x`
  --> <anon>:24:29
   |
15 |     let _ = thread::spawn(move || {
   |                           ------- value moved (into closure) here
...
24 |         println!("({},{})", enemy.x, enemy.y);
   |                             ^^^^^^^ value used here after move
   |

そういった場合には、Arc を使う。

Arc は、 Atomic でスレッドセーフな参照カウンタ付きポインタで、スレッド間で安全に所有権を共有することを可能にする。

Arc の中のデータはヒープ上にメモリが確保され、clone すると中の値へのポインタが作られる。

clone するたびに参照カウンタが増えていくが、それらの生存期間が終わるとカウンタは減り、0になるとデータが破棄される。

スレッド間での共有にはArc を使う

Arc を使うとこうなる。 ArcEnemy を包んで、使う前には clone する。

...
fn main() {
    // Arcで包む
    let enemy = Arc::new(Enemy { x: 0, y: 0});
    {
        // 使う時に clone する
        let enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                do_something(&enemy);
            }
        });
    }
    
    let enemy = enemy.clone();
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/e9clLQ

これはうまくいく。

もしあるスレッドでは敵を動かしたいんだという場合、これではうまくいかない。

&x で参照を共有するときと同じように、Arcで所有権を共有している場合データの競合を防ぐため、変更はできない。

ので、データを変更したい場合には更にもう1つ、 Mutex が必要になる。

データの変更も行う場合はMutex も使う

やろうと思ってもできないのはこんな感じ。敵を右に動かす

...
impl Enemy {
    fn move_right(&mut self) {
        self.x += 1;
    }
}

fn main() {
    let enemy = Arc::new(Enemy { x: 0, y: 0});
    {
        let mut enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                // ここで敵を動かしたい
                enemy.move_right();
            }
        });
    }
....
}

これは、 clone の時に mut で変更可能な値として使おうとしているが、 immutable なのでできないよとエラーになる。

error: cannot borrow immutable borrowed content as mutable
  --> <anon>:23:17
   |
23 |                 enemy.move_right();
   |                 ^^^^^ cannot borrow as mutable

Mutex を使って排他制御をするとデータの競合を防げるので、データの変更ができるようになる。

Arc の中のデータを Mutex で包み、中のデータを使う際には lock でロックを取得する。

取得したロックは、他のリソースと同様スコープを出ると自動で解放される。

ちなみに自分は lock の型を見てロックが取れなければエラーを返すものだと思っていたが、それは try_lock

lock の場合はロックが取れるまでブロックするので、他のスレッドがロックを解放してくれない場合はデッドロックする。

Mutex を使うとこうなる。

fn main() {
    // Mutexを追加
    let enemy = Arc::new(Mutex::new(Enemy { x: 0, y: 0}));
    {
        let enemy = enemy.clone();
        let _ = thread::spawn(move || {
            loop {
                thread::sleep(Duration::from_secs(1));
                // 使う前にロックを取得する
                let mut enemy = enemy.lock().unwrap();
                enemy.move_right();
            }
        });
    }
    
    let enemy = enemy.clone();
    for _ in 0..5 { 
        thread::sleep(Duration::from_secs(1));
        // 変更はしないけどこちらもロックを取る必要はある
        let enemy = enemy.lock().unwrap();
        println!("({},{})", enemy.x, enemy.y);
    }
}

https://is.gd/6DymJC

これだとxが変更されていくので出力はこうなる

(0,0)
(1,0)
(2,0)
(3,0)
(4,0)

Rustでぷよぷよ風ゲーム

最近遊んでいたおもちゃが形になってきた。

こんなん。ターミナルでプレイするぷよぷよ風ゲームのようなもの。(得点とかないし、まだゲームオーバーすらない)

f:id:totem_3:20170417230721g:plain

(だいぶカクカクしているのは agif の変換の仕方の問題が大きい)

Rust でマルチスレッドでプログラミングする方法の理解をもう少し深めようと思って書いていたが、一番大変だったのは ncurses で UI を書くところ。

ただしスレッドを使ったプログラミングについては少し理解が深まったのでそれはまた別途。

Mutex の挙動を勘違いしてデッドロックして悩んだり非常に無駄な時間を使っていた。

自分の場合はそもそもマルチスレッドの理解が浅いのでだいぶ基礎からやる必要があったけど、基本的にはここを読んでおけば大丈夫な気がする

qiita.com

一通り書いたあとでググったのでもっと速くググっておけば良かった。

Rust メモ Windows向けにクロスコンパイル

Windowsでも使いたいコードがあり、Linux上でWindows向けにクロスコンパイルをしてみたメモ。 rustupを使うと簡単にクロスコンパイルができる。

windows向けのtargetを探す

 $ rustup target list | grep win
i586-pc-windows-msvc
i686-apple-darwin
i686-pc-windows-gnu
i686-pc-windows-msvc
x86_64-apple-darwin
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc

今回は x86_64-pc-windows-gnu を追加する

rustup target add x86_64-pc-windows-gnu
 $ rustup target add x86_64-pc-windows-gnu
info: downloading component 'rust-std' for 'x86_64-pc-windows-gnu'
info: installing component 'rust-std' for 'x86_64-pc-windows-gnu'

これを --target にしてビルドするが、その前に mingw-w64 をインストール

 $ sudo apt install mingw-w64
Unpacking gcc-mingw-w64-base (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gcc-mingw-w64-i686.
Preparing to unpack .../gcc-mingw-w64-i686_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking gcc-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package g++-mingw-w64-i686.
Preparing to unpack .../g++-mingw-w64-i686_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking g++-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package mingw-w64-x86-64-dev.
Preparing to unpack .../mingw-w64-x86-64-dev_3.1.0-1_all.deb ...
Unpacking mingw-w64-x86-64-dev (3.1.0-1) ...
Selecting previously unselected package gcc-mingw-w64-x86-64.
Preparing to unpack .../gcc-mingw-w64-x86-64_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking gcc-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package g++-mingw-w64-x86-64.
Preparing to unpack .../g++-mingw-w64-x86-64_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking g++-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package g++-mingw-w64.
Preparing to unpack .../g++-mingw-w64_4.8.2-10ubuntu2+12_all.deb ...
Unpacking g++-mingw-w64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gcc-mingw-w64.
Preparing to unpack .../gcc-mingw-w64_4.8.2-10ubuntu2+12_all.deb ...
Unpacking gcc-mingw-w64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gfortran-mingw-w64-i686.
Preparing to unpack .../gfortran-mingw-w64-i686_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking gfortran-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gfortran-mingw-w64-x86-64.
Preparing to unpack .../gfortran-mingw-w64-x86-64_4.8.2-10ubuntu2+12_amd64.deb ...
Unpacking gfortran-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gfortran-mingw-w64.
Preparing to unpack .../gfortran-mingw-w64_4.8.2-10ubuntu2+12_all.deb ...
Unpacking gfortran-mingw-w64 (4.8.2-10ubuntu2+12) ...
Selecting previously unselected package gnat-mingw-w64-base.
Preparing to unpack .../gnat-mingw-w64-base_4.8.2-12ubuntu2+12.1_amd64.deb ...
Unpacking gnat-mingw-w64-base (4.8.2-12ubuntu2+12.1) ...
Selecting previously unselected package gnat-mingw-w64-i686.
Preparing to unpack .../gnat-mingw-w64-i686_4.8.2-12ubuntu2+12.1_amd64.deb ...
Unpacking gnat-mingw-w64-i686 (4.8.2-12ubuntu2+12.1) ...
Selecting previously unselected package gnat-mingw-w64-x86-64.
Preparing to unpack .../gnat-mingw-w64-x86-64_4.8.2-12ubuntu2+12.1_amd64.deb ...
Unpacking gnat-mingw-w64-x86-64 (4.8.2-12ubuntu2+12.1) ...
Selecting previously unselected package gnat-mingw-w64.
Preparing to unpack .../gnat-mingw-w64_4.8.2-12ubuntu2+12.1_all.deb ...
Unpacking gnat-mingw-w64 (4.8.2-12ubuntu2+12.1) ...
Selecting previously unselected package mingw-w64.
Preparing to unpack .../mingw-w64_3.1.0-1_all.deb ...
Unpacking mingw-w64 (3.1.0-1) ...
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Setting up binutils-mingw-w64-i686 (2.23.52.20130620-1ubuntu1+3build1) ...
Setting up binutils-mingw-w64-x86-64 (2.23.52.20130620-1ubuntu1+3build1) ...
Setting up mingw-w64-common (3.1.0-1) ...
Setting up mingw-w64-i686-dev (3.1.0-1) ...
Setting up gcc-mingw-w64-base (4.8.2-10ubuntu2+12) ...
Setting up gcc-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Setting up g++-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Setting up mingw-w64-x86-64-dev (3.1.0-1) ...
Setting up gcc-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Setting up g++-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Setting up g++-mingw-w64 (4.8.2-10ubuntu2+12) ...
Setting up gcc-mingw-w64 (4.8.2-10ubuntu2+12) ...
Setting up gfortran-mingw-w64-i686 (4.8.2-10ubuntu2+12) ...
Setting up gfortran-mingw-w64-x86-64 (4.8.2-10ubuntu2+12) ...
Setting up gfortran-mingw-w64 (4.8.2-10ubuntu2+12) ...
Setting up gnat-mingw-w64-base (4.8.2-12ubuntu2+12.1) ...
Setting up gnat-mingw-w64-i686 (4.8.2-12ubuntu2+12.1) ...
Setting up gnat-mingw-w64-x86-64 (4.8.2-12ubuntu2+12.1) ...
Setting up gnat-mingw-w64 (4.8.2-12ubuntu2+12.1) ...
Setting up mingw-w64 (3.1.0-1) ...

そしてビルドするとエラーが出る。こんな感じで。

 $ cargo build --target x86_64-pc-windows-gnu
   Compiling ... v0.1.0 ( )
error: linking with `gcc` failed: exit code: 1
  |
  = note: "gcc" "-Wl,--enable-long-section-names" "-fno-use-linker-plugin" "-Wl,--nxcompat" "-nostdlib" "-m64" "..." "..." "-L" "..." "..." "-o" "..." "-Wl,--gc-sections" "-nodefaultlibs" "-L" "..." "-L" "..." "-Wl,-Bstatic" "-Wl,-Bdynamic" "..." "..." "..." "..." "..." "..." "..." "..." "..." "..." "..." "-l" "ws2_32" "-l" "userenv" "-l" "shell32" "-l" "advapi32" "-l" "gcc_eh" "-lmingwex" "-lmingw32" "-lgcc" "-lmsvcrt" "-luser32" "-lkernel32" "..."
  = note: /usr/bin/ld: unrecognized option '--enable-long-section-names'
/usr/bin/ld: use the --help option for usage information
collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: Could not compile `dumpdir`.

To learn more, run the command again with --verbose.

既知の問題のようだ。

https://github.com/rust-lang/rust/issues/32859

ひとまずこれで回避できるとのこと。

 $ cat .cargo/config
[target.i686-pc-windows-gnu]
linker = "i686-w64-mingw32-gcc"

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

できた

 $ cargo build --target x86_64-pc-windows-gnu
   Compiling .... v0.1.0 (file:///.....)
    Finished debug [unoptimized + debuginfo] target(s) in 0.19 secs

補足

各種バージョン情報

 $ rustup --version
rustup 0.6.5 (88ef618 2016-11-04)
 $ rustc --version
rustc 1.13.0 (2c6933acc 2016-11-07)
 $ cargo --version
cargo 0.13.0-nightly (eca9e15 2016-11-01)

 $ uname -a
Linux workspace 3.13.0-92-generic #139-Ubuntu SMP Tue Jun 28 20:42:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
 $ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"

x86_64-pc-windows-msvc の情報

www.chriskrycho.com

Rust メモ String に対して match

Rust の match は非常に強力。

様々なパターンマッチや、 destructuring が使えて非常に便利

詳しくは

たまに String な変数に対して match を使いたいことがある。

が、こんなふうにやろうとしてももちろんできない。

sString なのに対して、 パターンである "hoge"&'static str なので型が一致しない。

String リテラルはないし、パターンの部分には enum variant や構造体、リテラルを書くことができるが(slice は experimentalだけど)、式を書くことはできない。

ので、"hoge".to_string() なんて書くことはできない。

つーことで String ではムリ。

だが、 &str ならできる。

そして &strString のビューに過ぎなかった。 String から &str を作るのは簡単でコストも小さい。

こんな感じで。

1つ目 &*s

Stringstr への Deref を実装しているので、 *s で dereference することで str が手に入るので &*s&str という寸法。

2つ目 &s[..]

String を 全体をスライシングしても &str が手に入る

3つ目は単純にメソッドがあるのでそれを使って &str に変換している

--

ついでに、 String から &str は安いが、&str から String にするのはメモリアロケーションが発生してそれなりにコストがかかるので、 match に限らず文字列を比較したい時には文字列リテラルto_string() とかで String にするより、今回のように String&str にしたほうがいい

Rust メモ Option | 値を取得して None で置き換える

Option を持つ構造体を扱っているときなどに、Option の値を取得してその後 None で置き換えたいことがある。

そんなときは take メソッド が使える。

Rust Playground

fn main() {
    let mut x = Some(10);

    // Some(v) に対して呼び出すと Some(v) が返ってくる
    assert_eq!(x.take(), Some(10));

    // x は None になってる
    assert_eq!(x, None);
 
    // None に対して呼び出すと None が返ってくる
    assert_eq!(x.take(), None);
}