ゆるスタック勉強会 #20 で「メタプログラミングとは」という内容で話してきました。「メタプログラミングって何?イメージがつかない」という話を受けて、イメージをつくようにするのがこの話の目的です。
本当はそれぞれの言語のやりかたについて手順を書けばよかったのですが、力つきたので、実際のライブラリから例を持ってくる方向性にしました。話している間はコードや別の資料も見ながら話していたので、このスライド自体というよりは、各スライドでポイントしているライブラリの実装あるいは類似するライブラリの実装を読んでもらうのがイメージをつかみやすいんじゃないかと思います。
それぞれの言語でニワカなところがあるので、全方位からツッコミを受けてもおかしくない内容だと思っています。何かあれば補足していきたいのでぜひどうぞ。
2016-04-01
Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer)
Rustは C FFI が強いので、はっきりしたstructとCの関数を呼び出すなんていうケースでは、それっぽいstructとexternでの関数の定義を書けば呼び出せる。コールバックもできる。
→他言語関数インターフェイス
しかし、以下のようなケースではちょっとやりにくい。
自分が必要だったところがC世界からのコールバック呼び出しだったので、それについても触れる。基本的には、RustのアプリケーションがCライブラリを使うという想定。コールバックなので、行ったり来たりなのだけどね。
上記がsrc/bridge.cに置かれているとする。
まず、Cargo.tomlにビルドスクリプトが「build.rs」にあるよ、ということを指示する。またコンパイラの実行の面倒を見てくれる「gcc」crateをbuild dependenciesに入れる(gccと言っているが、MSVCも対応しているらしい。試してないが)。
dependenciesのlibcは、ビルドスクリプト的には必要ないが、C FFIではほぼ使うのでここで入れておく。
上記で定義した「build.rs」に、コンパイラの呼び出しを定義する。
このあたりの記述、詳しくはCargoのドキュメント「Build Script Support」を参照。
本来ライブラリのリンクの指示も必要だが、ここでは一旦省く。詳しくは上記ドキュメントを参照(下でもすこし触れる)。
Cargoの流儀的には、FFIのためのRustらしくない定義はなんちゃら-sysというcrateに定義するほうがそれらしい。この部分だけの差し替えがしやすいから、とのこと。
必ずしもこうでなくてよいが、こうしておくことで「内側」をBox(ヒープ)で持つことが出来、ポインタに変換してC関数のポインタに渡すことができる。
git2-rsのsrc/transport.rsでやっていたので参考にした。
→他言語関数インターフェイス
しかし、以下のようなケースではちょっとやりにくい。
- 必要な関数の呼び出しにCマクロが必要
- opaqueなstructの下のほうのメンバにアクセスが必要
自分が必要だったところがC世界からのコールバック呼び出しだったので、それについても触れる。基本的には、RustのアプリケーションがCライブラリを使うという想定。コールバックなので、行ったり来たりなのだけどね。
やりかたの概要
やりかたは概ね以下の通り。- ラッパー関数をCで用意する
- CargoのビルドスクリプトでCソースのコンパイル方法を指示する
- ラッパー関数をexternで定義する
- ラッパー関数を呼び出すstruct & traitを定義する
- 実際に呼び出す
やりかた
ラッパー関数の準備
Rustから呼び出したい関数を、普通の関数呼び出しにできる形で定義する。本来は何かのコードのラッパーの想定なのだが、そっち側の知識が必要となると話がややこしくなるので、ここでは単なるCソースを例として使う。上記がsrc/bridge.cに置かれているとする。
Cargo.toml + build.rsにCソースのコンパイル方法の指示する
ここがこの記事のキモ。cargoに、置いたCソースのコンパイル方法を指示する必要がある。まず、Cargo.tomlにビルドスクリプトが「build.rs」にあるよ、ということを指示する。またコンパイラの実行の面倒を見てくれる「gcc」crateをbuild dependenciesに入れる(gccと言っているが、MSVCも対応しているらしい。試してないが)。
[package] (略) build = "build.rs" [build-dependencies] gcc = "0.3" [dependencies] libc = "0.2"
dependenciesのlibcは、ビルドスクリプト的には必要ないが、C FFIではほぼ使うのでここで入れておく。
上記で定義した「build.rs」に、コンパイラの呼び出しを定義する。
extern crate gcc;
fn main() {
gcc::compile_library("libbridgesample.a", &["src/bridge.c"]);
}
このあたりの記述、詳しくはCargoのドキュメント「Build Script Support」を参照。
本来ライブラリのリンクの指示も必要だが、ここでは一旦省く。詳しくは上記ドキュメントを参照(下でもすこし触れる)。
ラッパー関数をexternで定義する(いわゆる-sys部分)
ここからはもうRust onlyでのFFIと一緒。externでfnとstructを定義する。opaqueなstructをenumを定義するのがイディオムだそうだ。Cargoの流儀的には、FFIのためのRustらしくない定義はなんちゃら-sysというcrateに定義するほうがそれらしい。この部分だけの差し替えがしやすいから、とのこと。
ラッパー用のstructを用意する
ffi層を叩く、Rust 的構造にあわせた呼び出し部を用意する。今回は、コールバックをクロージャでもらい、それを実際のCコールバックから呼ぶ形にした。「外側」と「内側」の2つのstructを作る
Rust世界のコード用のデータを入れておく「内側」と、その「内側」とC関数からもらったポインタを格納しておく「外側」を用意する。必ずしもこうでなくてよいが、こうしておくことで「内側」をBox(ヒープ)で持つことが出来、ポインタに変換してC関数のポインタに渡すことができる。
git2-rsのsrc/transport.rsでやっていたので参考にした。
Drop traitを実装しポインタが自動で捨てられるようにする
後始末大事。コールバックとしてC側に渡す関数をexternで定義する
今回はライフタイムをRustが持っているので、キャストするだけ。ここでは、Rust側がライフタイムを握っているのでポインタを渡しているが、もしC側がライフタイムを握るようであれば、Box::into_rawとBox::from_rawをうまく使うといいと思う。ライフタイムの制御から外せる。mem::forget というのもあるかな。ラッパーを実装
あとはffi関数を呼び出すようMyStructにimplする。呼び出しコードを書く
実際呼ぶコードはこんなかんじになる。 結果はこんな感じ。mystruct_new Hello, World: 11 Hello Result: 12345 mystruct_freeこれでだいたい使えるはず。
おまけ:pkg-configでライブラリを参照させる
本来ラップする対象があるはずなので、リンク対象やincludeパスを指定する必要がある。基本的には、println!()で、cargoが必要な情報を渡してあげればよいが、build-dependenciesでpkg-config crateを使うと、pkg-configの結果を使える。例えばこんな感じ。 with-glib branchで使ってみてある。実際のところは、panicはやっつけなのでちゃんと分岐させたり、pkg-configに関わらず環境変数で設定できるようにしたりと、工夫がされていることが多いので、詳しくはそこらのライブラリのコードを参照。例えば、glib-sys crateはこんな感じ。2016-03-15
Rust の開発環境を作る (Ubuntu + Visual Studio Code)
今回はRustとその開発環境のセットアップの仕方について。Ubuntu 14.04環境に、Visual Studio Codeをインストールして、コード補完ができるようになるまで。Ubuntu Makeのところ以外は、Macでも大体同じじゃないかなと思う。
rustc的にはこれでおしまい。
便利なので本来のコマンド名codeでシンボリックリンクを貼っておく。
visual-studio-codeのリンクが壊れているようだけど、Gitでは直っているようなので、少しするとvisual-studio-codeでも起動できるようになると思う。
その後racerを実行するとわかるが、racerには参照用にRustのソースが必要なので、ダウンロードして適当なところに置いておく。
racer用にソースの配置場所を環境変数に設定する(~/.bashrcなどに書く)
racerコマンドを叩いてusageが出ればOK。
インストールはcargoで。
src/main.rsを適当に編集したら、Ctrl+Shift+Rか、コマンドパレット(Ctrl+Shift+p)からRun Debugで実行できる。
Rust コンパイラまわり
multirustのインストール
multirustは、stableやnightlyなどrust環境を切り替えられるようにするツール。普通にrustcをインストールしてもよいが、便利なのでmultirustで入れておく。$ curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh (略) $ rustc --version rustc 1.7.0 (a5d1e7a59 2016-02-29)
rustc的にはこれでおしまい。
Visual Studio Codeまわり
アーカイブを展開するだけでも良いのだけど、便利なUbuntu Makeツールを使う。Ubuntu Makeのインストール
$ sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make $ sudo apt-get update $ sudo apt-get install ubuntu-make新しいUbuntuではubuntu-makeのppaはいらないかもしれない。
Visual Studio Codeのインストール
$ umake ide visual-studio-code (インストールパスを聞かれるけどデフォルトでこんな感じに。) Choose installation path: /home/yoichi/.local/share/umake/ide/visual-studio-code
便利なので本来のコマンド名codeでシンボリックリンクを貼っておく。
$ ln -s ~/.local/share/umake/ide/visual-studio-code/code ~/.local/share/umake/bin/code
visual-studio-codeのリンクが壊れているようだけど、Gitでは直っているようなので、少しするとvisual-studio-codeでも起動できるようになると思う。
RustyCode extensionのインストール
VSCodeのRust言語用拡張のRustyCodeをインストールする。codeで起動後、Ctrl+Shift+pでコマンドパレットを起動し、install RustyCodeでインストール。開発支援ツールまわり(racer, rustfmt)
Codeを起動してRustyCodeで書こうとすると、以下のようにエラーが出るので、必要なツールを入れる。The "racer" command is not available. Make sure it is installed.
racer (コード補完ツール) のインストール
$ cargo install racer (略) Installing /home/yoichi/.multirust/toolchains/stable/cargo/bin/racer be sure to add `/home/yoichi/.multirust/toolchains/stable/cargo/bin` to your PATH to be able to run the installed binariescargoに言われる通りPATHを~/.bashrcなどに設定する
export PATH=$PATH:~/.multirust/toolchains/stable/cargo/bin
その後racerを実行するとわかるが、racerには参照用にRustのソースが必要なので、ダウンロードして適当なところに置いておく。
$ racer RUST_SRC_PATH environment variable must be set to point to the src directory of a rust checkout. E.g. "/home/foouser/src/rust/src" $ mkdir ~/src $ wget https://static.rust-lang.org/dist/rustc-1.7.0-src.tar.gz $ tar zxvf rustc-1.7.0-src.tar.gz
racer用にソースの配置場所を環境変数に設定する(~/.bashrcなどに書く)
export RUST_SRC_PATH=~/src/rustc-1.7.0/src
racerコマンドを叩いてusageが出ればOK。
rustfmt (ソースコードフォーマッター) のインストール
なくてもエラーは出ないが、入れておくとCtrl+Shift+I でコードフォーマットできる。cargo fmtでもOK.インストールはcargoで。
$ cargo install rustfmt
Hello Worldプロジェクトの作成
試すには、適当にプロジェクトを作成してCodeで開く。$ cargo new hello --bin $ code hello &
src/main.rsを適当に編集したら、Ctrl+Shift+Rか、コマンドパレット(Ctrl+Shift+p)からRun Debugで実行できる。
登録:
投稿 (Atom)
