🌮

Rust | include_bytes! で特定のファイルをコンパイル対象としてバイナリに含める

2024/07/16に公開

特定のファイルをコンパイル対象に含める

初期データとなる JSON ファイルをコンパイルに含めたいなと思い調べてみると、
include_bytes! というマクロが使えそうだったので試してみました!

include_bytes! マクロ

include_bytes! を使うことで、ファイルをバイト配列への参照として含めてくれます。

指定するファイルパスは現在のファイルからの相対パスです。

include_bytes! とファイル読み込み

include_bytes!File によるファイル読み込みの違いを確認するためのサンプルコードを書いてみました。

ディレクトリ構成は以下のようになっています。

root
├── src
│   ├── data
│   │   └── mountain.json
│   └── main.rs
├── Cargo.toml
└── Cargo.lock

JSON 形式のファイルを扱うので、serdeserde_json を使用しています。

Cargo.toml
[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 は以下です。

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);
}
data/mountain.json
{
  "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! を使います!

main.rs
fn main() {
    let msg = include_str!("data/sample.txt");
    println!("{}", msg);
}
$ cargo run
Hello, mountains and code!

まとめ

特定のファイルをコンパイル対象に含めたい場合は include_bytes!include_str! を使いましょう🔥

参考

コラボスタイル Developers

Discussion