データ圧縮ライブラリSnappyをRustで利用する
概要
タイトル通りです。というかTRPLのここの内容まんまです。
旧版日本語訳:https://doc.rust-jp.rs/the-rust-programming-language-ja/1.9/book/ffi.html
本家:https://doc.rust-lang.org/nomicon/ffi.html
いわゆるやってみた系ですがWindowsでやるとなんかつまづきが発生したのでメモする。
WindowsとLinux(Ubuntu)両方の手順載せます。
LinuxはWSLで検証したけどそこまで突っ込んだことしてないので多分大丈夫。
リポジトリにまとめた。見ながら読むとわかりやすいかも
github.com
目次
Snappyについて
Snappyのインストール
Snappyとのバインディングを書く
実行
Snappyについて
GitHub - google/snappy: A fast compressor/decompressor
C++で書かれたデータ圧縮解凍ライブラリ
zlibと比べて圧縮率は低い?が速度は大分速いらしい
MongoDBやChromeのIndexedDB(LevelDB)など著名なNoSQLデータベースの中で動いている http://google.github.io/snappy/
ちなみにubuntuのパッケージ管理ツールに同名のソフトウェアがあるがこれとは別物なので注意。
Snappyのインストール
Snappyのライブラリファイルを取得しましょう。
自分はここに9割ぐらい消耗しました。勢いに任せてソースコードをcmakeしようとするのはやめよう。(1敗)
もっと楽でわかりやすい方法があります。
Linux
sudo apt install libsnappy-dev
標準のパッケージマネージャでinstallする。
apt系以外の場合snappy
でパッケージの検索をかけてそれっぽいのを探す。
Windows
Windowsのパッケージ管理ツール Vcpkgをインストール。MicrosoftがOSSとして開発してて準公式的な雰囲気を感じる。
インストール方法の詳細は下記のqiita記事を参照。
Vcpkgを使用してOpenCVを導入する - Qiita
次にPowerShellを開いてVcpkgでSnappyをインストールする。
vcpkg install snappy:x64-windows
vcpkg install snappy
の様にパッケージ名のみの指定も可能だがデフォルトは32bitバージョンを取ってくるのでお好みで指定する。
Snappyとのバインディングを書く
cargo new --bin --edition 2018
してコードを書きます。取り敢えず下記の公式ドキュメントをみながら写経する。
他言語関数インターフェイス
※Windowsのみ
本質的なコードとは別にWindowsの場合はビルドスクリプトを書く必要があります。
コンパイル時、rustcに呼び出されたリンカはSnappyのライブラリファイルを見つけようとしますが、そのライブラリファイルが入っているVcpkgへのpathをリンカは知りません。
なので./srcがコンパイルされる前にリンカに教える必要があります。
Cargoがその辺をいい感じにしてくれる方法を提供しています。
Build Scripts - The Cargo Book
というわけでbuild.rsをプロジェクトのルートディレクトリに新規作成して、以下をコピペしましょう。
#[cfg(target_os = "windows")] fn main() { // Reference to https://doc.rust-lang.org/cargo/reference/build-scripts.html // Please rewrite <path_to_vcpkg> to your setted path. // static library(*.lib) println!("cargo:rustc-link-search=native=<path_to_vcpkg>/installed/x64-windows/lib/"); // dynamic library(*.dll) println!("cargo:rustc-env=PATH=<path_to_vcpkg>/installed/x64-windows/bin/"); } #[cfg(target_os = "linux")] fn main() { // Do nothing }
これがbuild.rsの内容になります。
専用の記法で標準出力することでライブラリのpathを設定することができます。
<path_to_vcpkg>
は自分のVcpkgへの絶対パスに書き換えてください。
余談
ぱっと全体をみて、まずmain関数が複数あることに違和感があるかもしれません。僕はありました。これはcfgアトリビュートにより条件ごとに実行対象のmain関数が変わるというものでvalidな書き方です。
下のmain関数の様になにもしなくてもエラーにはなりませんが、逆に実行できるmain関数がない場合はエラーとなります。
なので例えばWSLでこのプロジェクトをcargo runした場合、下のmain関数がないとビルドが落ちます。なにもしない関数があるのはその為です。
実行
ファイルの文字列を読み込んで圧縮したり解凍するサンプルコードです。
Snappyとのバインディングコードはbind_snappy.rsとして切り分けました。
mod bind_snappy; use bind_snappy::{compress, uncompress, max_compressed_length}; use std::io::{BufReader, Read}; use std::io::{BufWriter, Write}; use std::fs::File; fn main(){ /* something */ let file_path = "./src/bind_snappy.rs"; let mut file: File = File::open(file_path).unwrap(); let mut reader = BufReader::new(&mut file); let mut buf = String::new(); reader.read_to_string(&mut buf).expect("Could not read!"); let src: String = buf; let result: Vec<u8> = compress(src.as_bytes()); println!("Origin data: {:?}", src); println!("Origin length: {:?}", src.as_bytes().len()); println!("Compressed length: {:?}", result.len()); let bytes: Vec<u8> = uncompress(&result).unwrap(); // encode bytes to UTF-8 let utf8 = String::from_utf8(bytes.to_vec()).unwrap(); println!("Uncompressed: {:?}", utf8); assert_eq!(src, utf8) }
動いた。やったね
やった感想
ちゃんと圧縮されててスゲーって思った(小並感)
Cのライブラリ他にも使ってみたい
WebAssemblyに変換してJSからCのライブラリ扱うなんてこと出来たら面白そう