Const Functions#
- Source:
Rust’s const fn is similar to C++ constexpr - functions that can be
evaluated at compile time. Rust’s const evaluation is more restrictive but
guarantees deterministic behavior.
Basic const fn#
C++:
constexpr int square(int x) {
return x * x;
}
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int a = square(5); // compile-time
constexpr int b = factorial(5); // compile-time
int runtime_val = 10;
int c = square(runtime_val); // runtime
}
Rust:
const fn square(x: i32) -> i32 {
x * x
}
const fn factorial(n: u32) -> u32 {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
fn main() {
const A: i32 = square(5); // compile-time
const B: u32 = factorial(5); // compile-time
let runtime_val = 10;
let c = square(runtime_val); // runtime
}
Const Context#
const fn can be called in const contexts:
const fn compute() -> usize { 42 }
// Array size
const SIZE: usize = compute();
let arr: [i32; SIZE] = [0; SIZE];
// Static variables
static VALUE: i32 = square(10);
// Const generics
struct Buffer<const N: usize> {
data: [u8; N],
}
const fn buffer_size() -> usize { 1024 }
let buf: Buffer<{ buffer_size() }> = Buffer { data: [0; 1024] };
Const Limitations#
Rust’s const fn has more restrictions than C++ constexpr:
// Allowed in const fn:
const fn allowed() -> i32 {
let x = 5;
let y = if x > 0 { 10 } else { 20 };
let mut sum = 0;
let mut i = 0;
while i < 5 {
sum += i;
i += 1;
}
sum
}
// NOT allowed (as of Rust 1.70):
// - Heap allocation (Box, Vec, String)
// - Trait objects
// - Most iterator methods
// - Floating point operations (stabilizing)
// - Mutable references to non-local data
Const Generics#
C++ (non-type template parameters):
template<size_t N>
struct Buffer {
char data[N];
constexpr size_t size() const { return N; }
};
int main() {
Buffer<1024> buf;
}
Rust:
struct Buffer<const N: usize> {
data: [u8; N],
}
impl<const N: usize> Buffer<N> {
const fn new() -> Self {
Buffer { data: [0; N] }
}
const fn size(&self) -> usize {
N
}
}
fn main() {
let buf = Buffer::<1024>::new();
const SIZE: usize = buf.size();
}
Const Expressions in Types#
// Array with computed size
const fn array_len<T>() -> usize {
std::mem::size_of::<T>() * 8
}
struct BitSet<T> {
bits: [u8; std::mem::size_of::<T>()],
}
// Generic over const expression
fn process<const N: usize>(arr: [i32; N]) -> i32 {
let mut sum = 0;
let mut i = 0;
while i < N {
sum += arr[i];
i += 1;
}
sum
}
Const Trait Methods#
struct Point {
x: i32,
y: i32,
}
impl Point {
// const method
const fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
const fn origin() -> Self {
Point { x: 0, y: 0 }
}
const fn manhattan_distance(&self) -> i32 {
// abs() is const fn
self.x.abs() + self.y.abs()
}
}
const ORIGIN: Point = Point::origin();
const P: Point = Point::new(3, 4);
const DIST: i32 = P.manhattan_distance();
Compile-Time Assertions#
C++:
static_assert(sizeof(int) == 4, "int must be 4 bytes");
template<typename T>
constexpr void check_size() {
static_assert(sizeof(T) <= 16, "Type too large");
}
Rust:
// Using const evaluation
const _: () = assert!(std::mem::size_of::<i32>() == 4);
// In const fn
const fn check_size<T>() {
assert!(std::mem::size_of::<T>() <= 16, "Type too large");
}
// Compile-time check
const _: () = check_size::<i32>();
See Also#
Traits and Generics - Const trait bounds