-
Notifications
You must be signed in to change notification settings - Fork 435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rust: improve static_assert!
#269
base: rust
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I saw this, the first thing I thought was whether a type could be 'transmuted' into another. For example, whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So a comparison of the sizes? Or something more? I wonder if it is best to leave that to utilities on their own (e.g. like |
||
/// | ||
/// 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),+}) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These could have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the parenthesis around the expression? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, expressions can only be followed by some tokens. See my comments above. |
||
static_assert!( $(($expression) == ($a))||* ); | ||
}; | ||
(($expression:expr) is not in {$($a:expr),+}) => { | ||
static_assert!(!($(($expression) == ($a))||*)); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I do like maths notation, I'd probably make these use the square brackets as that is generally how you represent a collection in Rust. This isn't as relevant as the "in [ range ]" one though as this one can't be misunderstood when read in in isolation. |
||
|
||
// 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))); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would probably be better to use regular Rust range syntax (and |
||
|
||
// 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]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re: using ranges here: |
||
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pascal has an
in
operator. If we want to follow precedent, perhaps we could dropis
and just haveexpr in {a0, ..., aN}
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, Python too (as well as the
not in
). I am happy either way!