include_assets in your executable

include_assets provides convenient ways to include assets (arbitrary files) in a Rust binary. Assets are compressed and can either be looked up by file name or by variants of an enumeration.

assets by name

This is probably the most straightforward approach. Include all files in a directory with the include_dir!() macro, load (decompress) the assets at runtime using NamedArchive::load. Once they are loaded, use the NamedArchive more or less as you would a HashMap<&str, &[u8]>.

For examples, see the docs and examples/named/src/main.rs.

assets by enum variant

This approach might be a little unusual. Declare an enum with one unit variant per asset, and derive the trait EnumAsset using the derive macro that comes with this crate. Load the uncompressed assets using EnumArchive::<MyEnum>::load() (replacing MyEnum with whatever name you chose for your enum). Then look up the asset data via indexing (&archive[MyAsset::SomeVariant]) - this is infallible!

This approach has two distinct advantages:

A disadvantage is that you cannot iterate over assets. Additionally, their names are erased at runtime.

Despite the lack of iteration, asset data can be mapped using AssetEnum::map. You can then look up assets by enum variant in the resulting EnumMap. This may be useful for homogenous assets, you could for example parse templates, decode sound/image files, &c. Note that you can have multiple EnumAssets in the same program; mapping will be more useful if you have different enums for different types of asset.

For examples, see the docs and examples/enums/src/main.rs.

Build script

If you want to rebuild the executable whenever one of the assets changes, you should use a build.rs like this:

fn main() { println!("cargo:rerun-if-changed=path/to/assets"); println!("cargo:rerun-if-changed=more/assets"); }

Licence

This crate is licensed under the LGPL v3.

Compression

Currently supported: zstd, lz4, deflate, no compression.

Checksums

At compile time, a checksum is computed for each asset. These checksums are included in the binary. When loading/decompressing assets, the checksum of decompressed assets is compared against the compile-time checksum as a measure against data corruption and (more importantly) bugs.

Currently, blake2b is used for this, but this may change in the future.

Limitations

At runtime, main memory needs to be big enough to hold all assets at the same time in compressed and uncompressed form. At compile time, main memory needs to be big enough to hold all assets at the same time in compressed form and twice in uncompressed form.

The total size of each asset archive cannot exceed u32::MAX (4 GiB). Each asset archive can contain at most u32::MAX (roughly 4e9) distinct assets. If your use case exceeds these limits, reconsider if this is really the right approach.

usize is required to be at least 32 bits wide.

Related work

Rust core includes the include_bytes! macro which allows including a single file (uncompressed).

There are several crates which allow including compressed files, and even directories.

As far as I know, this crate is the only one which compresses included files as a whole rather than seperately. This approach has a significant disadvantage: To decompress a single file, all files have to be decompressed. However, it leads to better compression because the compression algorithm can take advantage of similarities between files in addition to similarities within each file.

Future work

Contributing

Bug reports are very welcome. Feature requests are also welcome, but no promises. I do not plan to accept patches at this time.