I'm porting some C code to Rust and that code contains a lot of things like this:
#define CONFIG_FLAG_NUMBER_23 1
#define THIS 10
#define THAT 11
#define THIS_AND_THAT (THIS + THAT)
#if CONFIG_FLAG_NUMBER_23
#define THIS_OR_THAT THIS
#else
#define THIS_OR_THAT THAT
#endif
#define ROOT_DIR "/root"
#define FILE_ONE ROOT_DIR "/file_one"
#define FILE_TWO ROOT_DIR "/file_two"
I decided to remove macros and replace them with constant expressions, but the attempts to do it in Rust weren't very successful:
static CONFIG_FLAG: bool = true;
static THIS: int = 10;
static THAT: int = 11;
static THIS_AND_THAT: int = THIS + THAT; // Okay, it works
// 1: Conditions
static THIS_OR_THAT: int = CONFIG_FLAG ? THIS : THAT; // Doesn't work, no conditional operator
static THIS_OR_THAT: int = if CONFIG_FLAG { THIS } else { THAT }; // Doesn't work, "if" is not basic enough for compile time
// 2: Strings
static ROOT_DIR: &'static str = "/root";
static FILE_ONE: &'static str = ROOT_DIR + "/file_one"; // Doesn't work, static strs can't be Added
static FILE_TWO: String = ROOT_DIR.to_string() + "/file_two"; // Don't even think about allocations in constant expressions!
static FILE_THREE: &'static str = concat!(ROOT_DIR, "/file_three"); // Doesn't work, concat! works only with literals
What would be the correct / least painful way to rewrite such code in Rust?
In Rust there is no preprocessor, and so many of these use cases are addressed differently.
The term macro refers to a family of features in Rust: declarative macros with macro_rules! and three kinds of procedural macros: Custom #[derive] macros that specify code added with the derive attribute used on structs and enums. Attribute-like macros that define custom attributes usable on any item.
A macro is a piece of code in a program that is replaced by the value of the macro. Macro is defined by #define directive. Whenever a macro name is encountered by the compiler, it replaces the name with the definition of the macro.
Problem 1: Conditional expressions
Since configuration flags can be interpreted as integers, they can be used as indexes in arrays of variants.
// Configuration flag
static CONFIG_FLAG: uint = 1;
// Variants of any static type
type T = &'static str;
static VARIANT1: T = "True";
static VARIANT2: T = "False";
// Now you can select, but you have to invert either flag, or variants, or your brain
static SELECTED: T = [VARIANT1, VARIANT2][1 - CONFIG_FLAG];
fn main() {
println!("{}", SELECTED);
}
Notes:
CONFIG_FLAG
of type bool currently doesn't work in array index because of issue #5873,
// error: can't cast str to uint
static SELECTED: T = [VARIANT1, VARIANT2][1 - CONFIG_FLAG_BOOL as uint];
therefore you have to create one more static item and then use it in conditionals.
static CONFIG_FLAG_UINT: uint = CONFIG_FLAG_BOOL as uint;
Problem 2: Compile time string concatenation
Simple C macros nicely map into Rust macros, so you can use essentially the same approach for string concatenation as in C. The only difference is that you have to explicitly use concat! instead of just placing literals next to each other.
#![feature(macro_rules)]
// Define new macro-string with name $name assembled from arguments $arg
macro_rules! define_str (
($name: ident, $($arg: expr), +)
=>
(macro_rules! $name (
() => (concat!($($arg), +))
));
)
// Some strings
define_str!(ROOT_DIR, "/root")
define_str!(SUB_DIR, ROOT_DIR!(), "/sub")
define_str!(FILE_NAME, SUB_DIR!(), "/file")
define_str!(NONSENSE, FILE_NAME!(), SUB_DIR!(), ROOT_DIR!())
fn main() {
println!("{}", FILE_NAME!());
println!("{}", NONSENSE!());
}
Notes:
I wanted to add !()
to macro names automatically inside of define_str
with additional macro like this,
macro_rules! add_bang_and_parens (
($arg: ident) => ($arg!());
($arg: expr) => ($arg);
)
but it seems like in macros pattern matching based on "types" of arguments is not currently possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With