BalaB
BalaB

Reputation: 3871

How to avoid hard-coded values in Rust

Below is a Maven/Java directory structure.

- src
  - main
    - java
    - resources
  - test
    - java
    - resources

- target

Here, the resources folder holds application-related configuration files and resource files, to avoid hard-coding their contents in source files.

How can I achieve the same in Rust with Cargo?

Upvotes: 3

Views: 2016

Answers (1)

Peter Hall
Peter Hall

Reputation: 58735

Maven doesn't include everything from your source. In fact, it includes the binary but doesn't even have to include the source at all in a .jar. You can configure it to say what to include, which by default is everything in the resources directory.

Cargo packages source code. Everything will be included in the crate, with the exception of things that match your .gitignore file. You can fine-tune that in your Cargo.toml file, by adding an include or exclude entry in the [package] section.

To access these files, there are a few options.

For example, if your project looks like this:

- Cargo.toml
- src
   - main.rs
- resources
   - hello.txt

There are three main ways to access hello.txt, as follows.

The include! macro

With the include! macro, you can access hello.txt from main.rs like this:

let hello: &str = include!("../resources/hello.txt");

Be aware that the macro will include the file directly in the source, exactly as if you copy & pasted the contents of the file. So the example I gave there would work only if the contents of the file include "" quotes. Any Rust source can go there, and it has to be included at compile-time. This can be convenient for including complicated Rust structs, without having to write parsing code. Notice that the path is relative to the source .rs file that included it.

The include_bytes! and include_str! macros

The include_bytes! macro creates a fixed-size array of u8 from the file, to be included at compile-time.

let bytes = include_bytes!("../resources/hello.txt").
let hello: String = String::from_bytes_lossy(bytes).to_string();

This is convenient for incorporating arbitrary binary data into your application, for example an image, without having to load it at runtime. The array has 'static lifetime, so will stay in memory for the entire lifetime of your application.

The include_str! macro works similarly, but will result in a string slice, also with 'static lifetime.

At runtime

To load a file at run-time, you can use:

let hello = std::fs::read_to_string("resources/hello.txt").unwrap();

The path here is relative to the root of your crate. This assuming that you are running the application with cargo run from where it was built. If you are deploying a binary, you may have to supply a path to where your application should find its resources.

Usually, this is the preferred method. It's more flexible as you can swap the config at runtime or use application arguments to load from a different location. There are lots of crates for parsing different file formats, including json, toml and plenty of others. You have control over the lifetime of the data you load, so you can make sure it's deallocated when you are finished with it.

Upvotes: 4

Related Questions