Constant, compile-time `default` implementations in Rust
You can find the source code here↗, the crate here↗ and the docs here↗.
The problem
You must have come across the Default
trait
several times while writing code in Rust, and it’s a great convenience for many. In case you haven’t,
it lets us get a default value for a given type. This can be extremely useful in cases where,
say if a value is not provided – you can default to a value appropriate for that type.
For example, say you have a CliArgs
struct that holds the configuration parsed from command line
arguments. It may look like this:
struct CliArgs {
superfast_mode: Option<u8>,
// ... some other fields ...
}
Let’s assume that our hypothetical superfast
mode that could have been there in our CLI arguments
will make our program use some magic to run faster, and a higher number will cause the program to run
even faster. Now, in the call site where we want to actually run the operation we would want to check
what superfast_mode
was set, and run accordingly. To do this, we might do something like:
fn run(args: CliArgs) -> std::io::Result<()> {
let mode = args.superfast_mode.unwrap_or_default();
if mode == 0 {
// run with default mode
run_default()
} else {
// run with magic
run_superfast_with_mode(mode)
}
}
The above is a trivial example, but you can see that it is very convenient to have a simple way to
get the default value for a type. However, there’s one caveat – due to the
limitations imposed by RFC 911,
we can’t use the Default
trait in constant contexts currently. Oops, that’s a bummer.
Say, for example you wanted a mutable static like this:
#[derive(Default)]
struct CurrentLoad {
diskload: usize,
netload: usize,
portload: [usize; 65535],
}
static mut GLOBAL_LOAD: CurrentLoad = CurrentLoad::default();
// And you wanted to do this
fn handle_new_disk_process(...) -> ... {
unsafe {
GLOBAL_LOAD.diskload += 1;
}
// ...
}
fn handle_new_port_process(port: u16, ...) -> ... {
unsafe {
GLOBAL_LOAD.portload[port as usize] += 1;
}
}
If you try to compile this, rustc
will complain with the error:
error[E0015]: calls in constants are limited to tuple structs and tuple variants
We expected that, didn’t we? But can we get around this….with some magic? Well, let’s see!
The solution
The solution at this point in time is a macro that can “look into” the types and provide the correct
values at compile-time (for use in const
contexts). Well, that’s exactly what the derived::Constdef↗ macro does. To solve the above situation,
this is all we need to do:
use derived::Constdef;
#[derive(Constdef)]
struct CurrentLoad {
diskload: usize,
netload: usize,
portload: [usize; 65535],
}
// now declare your static
static mut GLOBAL_LOAD: CurrentLoad = CurrentLoad::default();
// and do what you want! ...
fn handle_new_port_process(port: u16, ...) -> ... {
unsafe {
GLOBAL_LOAD.portload[port as usize] += 1;
}
}
Wait, … that’s all? Yeah! But what magic did the Constdef
macro do?
The (not so) magical part
Behind the scenes, the Constdef
peeks into the AST to look at the fields inside your struct.
It then checks if the fields can be initialized to default values in a constant context. If they
can, then Constdef
will substitute the requisite values in the impl. To achieve calling in
constant contexts, the macro will:
- Add a
pub const fn default() -> Self { ... }
associated method to your struct - Add a
Default
trait implementation to your struct so that you can use it with things likeOption::unwrap_or_default
ormem::take
Update (September 29, 2021)
The macro now:
- fully supports paths to primitives (
std::primitives::u8
orcore::primitives::u8
for example) - fully supports nesting:
- Nested arrays
- Nested tuples
- Nesting arrays in tuples
- Nesting tuples in arrays
Read more in this post.
What’s next?
This section is outdated. Read above
Currently, the Constdef
supports using a limited subset of types↗
in constant contexts. The next thing that is to be worked on is supporting nested arrays, followed
by supporting tuples. If you have any other ideas, drop them here↗ and let’s see what we can do to make them const
able!