pub fn black_box<T>(dummy: T) -> T
Expand description
An identity function that hints to the compiler to be maximally pessimistic about what
black_box
could do.
Unlike std::convert::identity
, a Rust compiler is encouraged to assume that black_box
can
use dummy
in any possible valid way that Rust code is allowed to without introducing undefined
behavior in the calling code. This property makes black_box
useful for writing code in which
certain optimizations are not desired, such as benchmarks.
Note however, that black_box
is only (and can only be) provided on a “best-effort” basis. The
extent to which it can block optimisations may vary depending upon the platform and code-gen
backend used. Programs cannot rely on black_box
for correctness, beyond it behaving as the
identity function. As such, it must not be relied upon to control critical program behavior.
This also means that this function does not offer any guarantees for cryptographic or security
purposes.
§When is this useful?
While not suitable in those mission-critical cases, black_box
’s functionality can generally be
relied upon for benchmarking, and should be used there. It will try to ensure that the
compiler doesn’t optimize away part of the intended test code based on context. For
example:
fn contains(haystack: &[&str], needle: &str) -> bool {
haystack.iter().any(|x| x == &needle)
}
pub fn benchmark() {
let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
let needle = "ghi";
for _ in 0..10 {
contains(&haystack, needle);
}
}
The compiler could theoretically make optimizations like the following:
- The
needle
andhaystack
do not change, move the call tocontains
outside the loop and delete the loop - Inline
contains
needle
andhaystack
have values known at compile time,contains
is always true. Remove the call and replace withtrue
- Nothing is done with the result of
contains
: delete this function call entirely benchmark
now has no purpose: delete this function
It is not likely that all of the above happens, but the compiler is definitely able to make some
optimizations that could result in a very inaccurate benchmark. This is where black_box
comes
in:
use std::hint::black_box;
// Same `contains` function
fn contains(haystack: &[&str], needle: &str) -> bool {
haystack.iter().any(|x| x == &needle)
}
pub fn benchmark() {
let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
let needle = "ghi";
for _ in 0..10 {
// Adjust our benchmark loop contents
black_box(contains(black_box(&haystack), black_box(needle)));
}
}
This essentially tells the compiler to block optimizations across any calls to black_box
. So,
it now:
- Treats both arguments to
contains
as unpredictable: the body ofcontains
can no longer be optimized based on argument values - Treats the call to
contains
and its result as volatile: the body ofbenchmark
cannot optimize this away
This makes our benchmark much more realistic to how the function would actually be used, where arguments are usually not known at compile time and the result is used in some way.