build_assert
build_assert
allows you to make assertions at build-time.
Unlike assert
and some implementations of compile-time assertions, such as static_assertions
, build_assert
works before runtime, and can be used for expressions containing const generics.
Usage
Add build_assert
to your project by running cargo add
:
cargo add build_assert
Examples
fn foo<const N: usize>() {
build_assert!(N > 5);
}
foo::<10>(); // Fine.
foo::<0>(); // Fails to compile.
The above example will fail to build in release mode. Due to the internal implementation, it will pass the build and panic at runtime in debug mode.
As a comparison, assert
will only panic at runtime, and static assertion implementations can not be applied to const generics:
macro_rules! static_assert {
($e:expr) => {
const _: () = core::assert!($e);
};
}
fn foo<const N: usize>() {
static_assert!(N > 5);
}
An error occurs when compiling the above example:
error[E0401]: can't use generic parameters from outer item
--> src/lib.rs:36:18
|
9 | fn foo<const N: usize>() {
| - const parameter from outer item
10 | static_assert!(N > 5);
| ^ use of generic parameter from outer item
Features
By default, build_assert
uses inline assembly (i.e. core::arch::asm
) to raise build-time errors. If you need to build with this crate on a target that does not support inline assembly (see the Rust reference), you can enable the no_asm
feature.
When no_asm
is enabled, build_assert
raises a link error by referencing an undefined symbol if the assertion fails. By default, the symbol name is __build_error_impl
. To avoid symbol conflicts, you can set the environment variable BUILD_ERROR_SYM
to specify a different symbol before building:
BUILD_ERROR_SYM=hello cargo build --release
Note that if the project has been previously built, the build cache should be cleared to ensure this change takes effect.
Under the Hood
The build_assert
macro will be expanded to:
if !cond {
build_error!();
}
In release mode, the condition of if
expression is expected to be evaluated by the optimizer. If cond
is true
, the results of build_error
macro expansion will be optimized away. Otherwise, the expansion results will be retained.
On targets that support inline assembly, the build_error
macro will expand to:
core::arch::asm!("build error at file.rs:line:column");
Since build
is not a valid instruction on any target, the build will fail.
On targets that do not support inline assembly, the build_error
macro will expand to:
extern "Rust" {
fn __build_error_impl() -> !;
}
unsafe { __build_error_impl() }
It raises a link error like this:
error: linking with `cc` failed: exit status: 1
|
= note: env -u ...
= note: /usr/bin/ld: ... .o: in function `rust_out::main::...':
... .rs:6: undefined reference to `__build_error_impl'
collect2: error: ld returned 1 exit status
= note: ...
In debug mode, since the optimizer will not run, the build_error
macro will always be retained. We cannot raise build errors using the above method, otherwise no matter whether the condition is true
or not, the build will always fail. So the build_error
macro will expand to a panic
.
References
The idea of build_assert
macro came from the Rust for Linux project. This crate uses a different approach to implement the macro.
Changelog
See CHANGELOG.md.
License
Copyright (C) 2023 MaxXing. Licensed under either of Apache 2.0 or MIT at your option.