Skip to content

Commit

Permalink
rust: improve static_assert!
Browse files Browse the repository at this point in the history
Now `static_assert!` allows for several forms:

  - Boolean assertion: `expr`.
  - Set membership assertion: `(expr) is in {a0, ..., aN}`.
  - Interval membership assertion: `(expr) is in [min, max]`.
  - Fits-in-type assertion: `(expr) fits in type`.

These are all `static_assert!`s instead of being split into
a separate macro (e.g. `fits_in!`) because we want to ensure
they are only within/as a `static_assert!` (e.g. for the `i128`
trick).

The documentation is also improved for the boolean assertion case.

Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
  • Loading branch information
ojeda committed May 13, 2021
1 parent fc2b177 commit dd89a2e
Showing 1 changed file with 166 additions and 3 deletions.
169 changes: 166 additions & 3 deletions rust/kernel/static_assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,198 @@

/// Static assert (i.e. compile-time assert).
///
/// Similar to C11 [`_Static_assert`] and C++11 [`static_assert`].
/// There are several forms of this macro:
///
/// - Boolean assertion: `expr`.
/// - Set membership assertion: `(expr) is in {a0, ..., aN}`.
/// - Interval membership assertion: `(expr) is in [min, max]`.
/// - Fits-in-type assertion: `(expr) fits in type`.
///
/// The expressions in all the forms are evaluated in [const context].
///
/// [const context]: https://doc.rust-lang.org/reference/const_eval.html
///
/// # Boolean assertion: `expr`
///
/// Statically asserts the given expression.
///
/// Similar to C11 [`_Static_assert`] and C++11 [`static_assert`].
/// The feature may be added to Rust in the future: see [RFC 2790].
///
/// [`_Static_assert`]: https://en.cppreference.com/w/c/language/_Static_assert
/// [`static_assert`]: https://en.cppreference.com/w/cpp/language/static_assert
/// [RFC 2790]: https://github.com/rust-lang/rfcs/issues/2790
///
/// # Examples
/// ## Examples
///
/// ```
/// // Trivial assert.
/// static_assert!(42 > 24);
///
/// // Assert on sizes, similar to C's `sizeof(T)`.
/// static_assert!(core::mem::size_of::<u8>() == 1);
///
/// // Assert on binary string.
/// const X: &[u8] = b"bar";
/// static_assert!(X[1] == 'a' as u8);
/// static_assert!(X[1] == b'a');
///
/// // Check we uphold some constraint from the C side by testing the bindings.
/// static_assert!(RUST_BUFFER_SIZE >= bindings::LOG_LINE_MAX);
///
/// // Calling `const fn`s is possible.
/// const fn f(x: i32) -> i32 {
/// x + 2
/// }
/// static_assert!(f(40) == 42);
/// ```
///
/// # Set membership assertion: `(expr) is in {a0, ..., aN}`
///
/// Statically asserts that the given expression (typically a `const` integer) is in a set.
/// The negated form (`is not in`) is also available.
///
/// ## Examples
///
/// ```
/// // Trivial usage.
/// static_assert!((-2) is not in {-1, 0, 2});
/// static_assert!((-1) is in {-1, 0, 2});
/// static_assert!(( 0) is in {-1, 0, 2});
/// static_assert!(( 1) is not in {-1, 0, 2});
/// static_assert!(( 2) is in {-1, 0, 2});
/// static_assert!(( 3) is not in {-1, 0, 2});
///
/// // Typical usage.
/// static_assert!((SOME_CONSTANT_DEPENDING_ON_ARCH) is in {FOO, BAR, BAZ});
/// static_assert!((core::mem::size_of::<usize>()) is in {4, 8});
/// ```
///
/// # Interval membership assertion: `(expr) is in [min, max]`
///
/// Statically asserts that the given expression (typically a `const` integer) is in a closed
/// interval (i.e. inclusive range). The negated form (`is not in`) is also available.
///
/// ## Examples
///
/// ```
/// // Trivial usage.
/// static_assert!((-2) is not in [-1, 2]);
/// static_assert!((-1) is in [-1, 2]);
/// static_assert!(( 0) is in [-1, 2]);
/// static_assert!(( 1) is in [-1, 2]);
/// static_assert!(( 2) is in [-1, 2]);
/// static_assert!(( 3) is not in [-1, 2]);
///
/// // Typical usage.
/// static_assert!((FOO) is in [MIN_FOO, MAX_FOO]);
/// ```
///
/// # Fits-in-type assertion: `(expr) fits in type`
///
/// Statically asserts that the given expression (typically a `const` integer) fits in the given
/// type (which must provide `T::MIN` and `T::MAX`). The negated form (`does not fit in`) is also
/// available.
///
/// Casting a "kernel integer" (i.e. up to [`i64`]/[`u64`]) to [`i128`] within the expression is
/// allowed to easily manipulate integers: no 128-bit code will be generated since it will be
/// evaluated in a const context.
///
/// ## Examples
///
/// ```
/// // Trivial usage.
/// static_assert!(( -1) does not fit in u8);
/// static_assert!(( 0) fits in u8);
/// static_assert!((255) fits in u8);
/// static_assert!((256) does not fit in u8);
///
/// // Two's complement.
/// static_assert!((-128) fits in i8);
/// static_assert!(( 127) fits in i8);
/// static_assert!(( 128) does not fit in i8);
///
/// // Using `i128` for easy manipulation of integers.
/// const MAX_ERRNO: u32 = 4095;
/// static_assert!((-(MAX_ERRNO as i128)) fits in i16);
/// ```
#[macro_export]
macro_rules! static_assert {
// Boolean assertion: `expr`.
($condition:expr) => {
// Based on the latest one in `rustc`'s one before it was [removed].
//
// [removed]: https://github.com/rust-lang/rust/commit/c2dad1c6b9f9636198d7c561b47a2974f5103f6d
#[allow(dead_code)]
const _: () = [()][!($condition) as usize];
};

// Set membership assertion: `(expr) is in {a0, ..., aN}`.
(($expression:expr) is in {$($a:expr),+}) => {
static_assert!( $(($expression) == ($a))||* );
};
(($expression:expr) is not in {$($a:expr),+}) => {
static_assert!(!($(($expression) == ($a))||*));
};

// Interval membership assertion: `(expr) is in [min, max]`.
(($expression:expr) is in [$min:expr, $max:expr]) => {
static_assert!( ($expression) >= ($min) && ($expression) <= ($max) );
};
(($expression:expr) is not in [$min:expr, $max:expr]) => {
static_assert!(!(($expression) >= ($min) && ($expression) <= ($max)));
};

// Fits-in-type assertion: `(expr) fits in type`.
(($expression:expr) fits in $t:ty) => {
static_assert!(($expression) is in [<$t>::MIN as i128, <$t>::MAX as i128]);
};
(($expression:expr) does not fit in $t:ty) => {
static_assert!(($expression) is not in [<$t>::MIN as i128, <$t>::MAX as i128]);
};
}

// Tests.
//
// These should later on go into a proper test.

static_assert!(42 > 24);
static_assert!(core::mem::size_of::<u8>() == 1);

const X: &[u8] = b"bar";
static_assert!(X[1] == b'a');

const fn f(x: i32) -> i32 {
x + 2
}
static_assert!(f(40) == 42);

static_assert!((-2) is not in {-1, 0, 2});
static_assert!((-1) is in {-1, 0, 2});
static_assert!(( 0) is in {-1, 0, 2});
static_assert!(( 1) is not in {-1, 0, 2});
static_assert!(( 2) is in {-1, 0, 2});
static_assert!(( 3) is not in {-1, 0, 2});

static_assert!((core::mem::size_of::<usize>()) is in {4, 8});

static_assert!((-2) is not in [-1, 2]);
static_assert!((-1) is in [-1, 2]);
static_assert!(( 0) is in [-1, 2]);
static_assert!(( 1) is in [-1, 2]);
static_assert!(( 2) is in [-1, 2]);
static_assert!(( 3) is not in [-1, 2]);

static_assert!((-129) does not fit in i8);
static_assert!((-128) fits in i8);
static_assert!(( 127) fits in i8);
static_assert!(( 128) does not fit in i8);

static_assert!(( -1) does not fit in u8);
static_assert!(( 0) fits in u8);
static_assert!((255) fits in u8);
static_assert!((256) does not fit in u8);

const MAX_ERRNO: u32 = 4095;
static_assert!((-(MAX_ERRNO as i128)) fits in i16);
static_assert!((-(MAX_ERRNO as i128)) does not fit in i8);
static_assert!((-(MAX_ERRNO as i128)) does not fit in u16);

0 comments on commit dd89a2e

Please sign in to comment.