5

I am working on some code where a buffer is backed by a statically sized array. Since Rust and the build tools provided by it offer the possibilities to compile conditionally, I can do something like this:

struct Buffer {
    // default case, if none is set
    #[cfg(not(buffersize))]
    buffer: [f32; 16],

    #[cfg(buffersize = "32")]
    buffer: [f32; 32],

    #[cfg(buffersize = "64")]
    buffer: [f32; 64],
}

impl Buffer {
    fn new() -> Buffer {
        Buffer {
            #[cfg(not(buffersize))]
            buffer: [0.0; 16],

            #[cfg(buffersize = "32")]
            buffer: [0.0; 32],

            #[cfg(buffersize = "64")]
            buffer: [0.0; 64],
        }
    }
}

There is another question that uses features to compile the code conditionally. Using features alone, I would have to combine buffersize and the actual value e.g. buffersize16. Is it possible to provide the cfg flags to Cargo, or would I need to provide them directly to rustc?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    According to [this answer](https://stackoverflow.com/questions/34777302/example-of-how-to-use-conditional-compilation-macros-in-rust) I have to use features. Is that correct? –  Aug 03 '19 at 13:22
  • Why do you need to do this? Is there any reason you cannot expect user to pass a slice as a buffer instead of this Buffer object? This looks very cumbersome, especially when you might want to have two different buffer sizes at once. –  Aug 03 '19 at 13:51
  • There are multiple factors at play here: first `Buffer`is just a wrapper for a statically sized array. I could have used a type alias eg. `type Buffer = [f32; 256]` and implement the necessary methods on it. The `Buffer` will eventually be used in some concurrent context / will be processed in multiple threads. Working with correct lifetimes seems to be more cumbersome here, but I get your point, that the provided example is not very elegant. –  Aug 03 '19 at 14:14
  • That's fair, what about multiple buffer size problem? I'd suggest just going with multiple structs, or a generic struct and let user choose what size they need before creating the buffer, not at compile time, even if they will ever need a single buffer type. (user is general, even if you're writing code just for yourself) –  Aug 03 '19 at 14:45

2 Answers2

14

You can set the environnment variable RUSTFLAGS or set rustflags variable in .cargo/config.
From environment-variables

RUSTFLAGS — A space-separated list of custom flags to pass to all compiler invocations that Cargo performs. In contrast with cargo rustc, this is useful for passing a flag to all compiler instances.

In your example, you could use :

RUSTFLAGS='--cfg buffersize="32"' cargo build
mpromonet
  • 11,326
  • 43
  • 62
  • 91
2

I want to post an update to my question as an additional option on how to pass (numeric) configuration values at compile time, that's possible through a build script.

Suppose you have following build script inside your project:

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

fn main() {
    println!("cargo:rerun-if-env-changed=SIZE");
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest = Path::new(&out_dir).join("consts.rs");

    let mut out_file = File::create(&dest).expect("Cannot create file");
    let size: usize = env!("SIZE").parse().unwrap();

    write!(&out_file, "pub const S : usize = {};", size);
}

It reads an environment variable at compile time, parses it as usize and writes a rust file (consts.rs) containing only the constant. Now, in your application code you can include this file and use the constant to eg. allocate memory on the stack:

include!(concat!(env!("OUT_DIR"), "/consts.rs"));

fn main() {
    let array = [0.0f32; S];
    println!("array len= {:?}", array.len());
}

The downside of this trick is, that you have to recompile the whole project (or parts of it) whenever the value of the environment variable changes, since cargo:rerun-if-env-changed=SIZE doesn't get captured. It also implies to always keep knowledge of this configuration option, but this could be wrapped in an additional build script like a makefile. Even if this is not the most elegant path to choose, it might be an option on certain occasions.

It would be nice to have this as macro option, though.