Rust | include_bytes! で特定のファイルをコンパイル対象としてバイナリに含める
特定のファイルをコンパイル対象に含める
初期データとなる JSON ファイルをコンパイルに含めたいなと思い調べてみると、
include_bytes!
というマクロが使えそうだったので試してみました!
include_bytes! マクロ
include_bytes!
を使うことで、ファイルをバイト配列への参照として含めてくれます。
指定するファイルパスは現在のファイルからの相対パスです。
include_bytes! とファイル読み込み
include_bytes!
と File
によるファイル読み込みの違いを確認するためのサンプルコードを書いてみました。
ディレクトリ構成は以下のようになっています。
root
├── src
│ ├── data
│ │ └── mountain.json
│ └── main.rs
├── Cargo.toml
└── Cargo.lock
JSON 形式のファイルを扱うので、serde と serde_json を使用しています。
[package]
name = "rust-sandbox"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
main.rs は以下です。
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Mountain {
id: i32,
name: String,
elevation: i32,
}
// ファイルパス
const FILE_PATH: &str = "src/data/mountain.json";
// JSON ファイルをコンパイルに含める
const MOUNTAIN_JSON_BYTES: &'static [u8] = include_bytes!("data/mountain.json");
fn main() {
// include_bytes! したデータを出力
let mountain: Mountain =
serde_json::from_slice(MOUNTAIN_JSON_BYTES).unwrap_or_else(|e| panic!("{}", e));
println!("{:?}", mountain);
// ファイルの読み込んで結果を出力
let file = File::open(FILE_PATH).unwrap_or_else(|e| panic!("{}", e));
let reader = BufReader::new(file);
let mountain: Mountain = serde_json::from_reader(reader).unwrap_or_else(|e| panic!("{}", e));
println!("{:?}", mountain);
}
{
"id": 1,
"name": "Everest",
"elevation": 8848
}
cargo run の実行結果です。
include_bytes!
も成功して、ファイルパスが正しいので正常に出力されています。
$ cargo run
Mountain { id: 1, name: "Everest", elevation: 8848 }
Mountain { id: 1, name: "Everest", elevation: 8848 }
ビルドした Rust バイナリを直接実行してみます。
こちらも、include_bytes!
も成功して、ファイルパスが正しいので正常に出力されています。
$ ./target/debug/rust-sandbox
Mountain { id: 1, name: "Everest", elevation: 8848 }
Mountain { id: 1, name: "Everest", elevation: 8848 }
では、mountain.json
を別のフォルダに移動させて実行してみます。
そうすると、include_bytes!
は成功しますが、ファイルが見つからないため No such file or directory (os error 2)
エラーが発生します。
$ ./target/debug/rust-sandbox
Mountain { id: 1, name: "Everest", elevation: 8848 }
thread 'main' panicked at src/main.rs:25:57:
No such file or directory (os error 2)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
また、mountain.json
の内容を変更すると、ファイル読み込み側は当然変更内容が出力結果に反映されます。
$ ./target/debug/rust-sandbox
Mountain { id: 1, name: "Everest", elevation: 8848 }
Mountain { id: 2, name: "K2", elevation: 8861 }
以上!!
想定した結果が得られました🙌✨
バイトではなく文字列とする場合
バイト配列ではなく、文字列としてコンパイルに含めることもできます。
その場合は include_str!
を使います!
fn main() {
let msg = include_str!("data/sample.txt");
println!("{}", msg);
}
$ cargo run
Hello, mountains and code!
まとめ
特定のファイルをコンパイル対象に含めたい場合は include_bytes!
や include_str!
を使いましょう🔥
Discussion