This is one area that Modern C++ is ahead of Rust. C++ lets you do some amazing compile-time calculations, and is very flexible about making functions constexpr
. The downside being that constexpr
doesn't guaranty that a function is constant---it's more like a suggestion.
You've used these already:
const MY_CONST: usize = 12;
They are great, and work just like you'd expect. Set a value, and it works everywhere.
You can do calculations in constants:
const A: usize = 5;
const B: usize = 6;
const C: usize = A * B;
These are evaluated at compile time. If A
and B
are never used, other than as parts of the calculation---they will be eliminated from optimized builds.
You can use compile-time declarations to adjust constants depending upon compilation environment:
#[cfg(debug)]
const A: usize = 3;
#[cfg(release)]
const A: usize = 4;
A debug
build will have a different value to a release
build. You might gate by the type of operating system on which the program is compiled:
#[cfg(target_family = "unix")]
const A: usize = 3;
#[cfg(not(target_family = "unix"))]
const A: usize = 4;
fn main() {
println!("{A}");
}
(The Rust Playground is running a Unix!)
You can add the const
keyword to functions to have the function execute at compile time:
const fn add(a: usize, b: usize) -> usize {
a + b
}
const A: usize = add(4, 6);
fn main() {
println!("{A}");
}
You can even use the constant function with dynamic inputs:
const fn add(a: usize, b: usize) -> usize {
a + b
}
const A: usize = add(4, 6);
fn main() {
let mut i = 5;
i += 3;
println!("{}", add(A, i));
println!("{A}");
}
So let's try and make a more complicated constant function:
const fn loopy() -> usize {
let mut n = 1;
for i in 0..20 {
n += i;
}
n
}
const A: usize = loopy();
fn main() {
println!("{A}");
}
We've added a mutable variable inside a constant. Surely that will fail? No! The error is that you aren't allowed to use for
loops in constant functions. This is because the Iterator
type is inherently not constant.
Transforming to a while
loop works fine:
const fn loopy() -> usize {
let mut n = 1;
let mut i = 0;
while i < 20 {
n += i;
i += 1;
}
n
}
You can offload a fair amount of work this way: calculating constants up-front means one less calculation to do at run-time.
Unfortunately, there's a pretty big list of things you can't do at compile time --- yet. The Rust Core team are improving this.
- You can't use floating point numbers, except as direct constants.
const n: f32 = 1.0
is fine. A function using floating point numbers won't work. - You can't use iterators.
- You can't connect to external data sources - OTHER than files. The macros
include_str!
andinclude_bytes!
can embed files in your binary.