1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
/*!
Functionality for asserting statements at compile time, including those using const and type generics.
It works by trying to evaluate a constant, and failing (via panicking at compile time) if the expression evaluates to false.
Since `cargo check` does not evaluate constants, `static_assert!`s with specified generics do not show up as errors,
and full `cargo build` compilations are needed instead.
This is a rather 'hack'y method of doing asserts, so I wouldn't be that surprised if future versions of rust break it.
For now, it still works as of 1.77.2.
Attempts to add const generic functionality in the `static_assert` crate [have been made](https://github.com/nvzqz/static-assertions/issues/40),
but it doesn't seem like it'll be added anytime soon.
These asserts are not present in function signatures or the type system in any way, possibly making it hard to reason about when creating any kind of abstraction.
You should probably use them sparingly, and explicitly document them in functions that rely on static asserts.
# Examples
Asserting constant expressions:
```
static_assert!(() 1 + 2 < 17); // True statement, compiles.
static_assert!(() 45 * 25 < 3); // False statement, does not compile:
// error[E0080]: evaluation of constant value failed
// | static_assert!(() 45 * 25 < 3)
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Static assert failed.'
```
\
The error message is optionally specified:
```
static_assert!(() 45 * 25 < 3 => "This is the error message!");
// the evaluated program panicked at 'This is the error message!'
```
\
Putting an assert outsize of a function block will cause an syntax error.
To get around that you can assign it to a constant of type unit,
but since you can't capture any generics outside of a function block, you might as well just use `assert!`.
```
const FOO: () = static_assert!(() 1 + 1 == 2);
const BAR: () = assert!(1 + 1 == 2);
```
\
It can error conditionally, depending on the value of the generic:
```
fn foo<const N: usize>() {
// Const generics used in the expression have to be passed in explicitly, along with their type.
static_assert!((N: usize) N != 0 => "N must be a non-zero value!");
// Some other functionality.
}
```
`foo::<0>()` will not show an error when using cargo check.
However, attempting to compile still results in an error, as expected.
```
// error[E0080]: evaluation of `foo::Assert::<0>::CHECK` failed
// | static_assert!((N: usize) N != 0 => "N must be a non-zero value!");
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'N must be a non-zero value!'
//
// note: the above error was encountered while instantiating `fn main::foo::<0>`
// | foo::<0>();
```
\
Not declaring the generics present in the expression results in an error.
```
fn bar<const N: usize>() {
static_assert!(() N != 0 => "N must be a non-zero value!");
// can't use generic parameters from outer item
}
```
\
Type generics can be used as well.
```
fn baz<T>() {
static_assert!((T) std::mem::size_of::<T>() == 4 => "T must be 4 bytes long!");
}
```
\
Unsized types need to be passed with this syntax:
```
fn baz<U: ?Sized>() {
static_assert!((U?) true => "There isn't much you can statically check about unsized types.");
}
```
\
Multiple generics can be used at a time.
```
fn baz<const N: usize, const M: usize, T>() {
static_assert!((N: usize, M: usize) N > M => "N must be greater than M!");
static_assert!((N: usize, T) N == std::mem::size_of::<T>() / 2 => "N must be half the size of T!");
}
baz::<4, 7, u64>(); // panics at "N must be greater than M!"
baz::<4, 1, u8>(); // panics at "N must be half the size_of T!"
```
*/
enum Generic {
Type(syn::Ident),
UnsizedType(syn::Ident),
Const(syn::Ident, syn::Type),
}
impl Generic {
pub fn definition(&self) -> proc_macro2::TokenStream {
match self {
Generic::Type(i) => quote::quote! { #i, },
Generic::UnsizedType(i) => quote::quote! { #i: ?Sized, },
Generic::Const(i, t) => quote::quote! { const #i: #t, },
}
}
pub fn placement(&self) -> proc_macro2::TokenStream {
match self {
Generic::Type(i) => quote::quote! { #i, },
Generic::UnsizedType(i) => quote::quote! { #i, },
Generic::Const(i, _t) => quote::quote! { #i, },
}
}
pub fn placement_type(&self) -> Option<proc_macro2::TokenStream> {
match self {
Generic::Type(_i) => Some(self.placement()),
Generic::UnsizedType(_i) => Some(self.placement()),
Generic::Const(_i, _t) => None,
}
}
}
impl syn::parse::Parse for Generic {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = input.parse()?;
Ok(if input.parse::<syn::Token![:]>().is_ok() {
if let Ok(qm) = input.parse::<syn::Token![?]>() {
return Err(syn::Error::new(qm.span, format!("Syntax error, if you want to make the type unsized do {ident}? instead of {ident}: ?Sized.")))
}
Generic::Const(ident, input.parse()?)
} else if input.parse::<syn::Token![?]>().is_ok() {
Generic::UnsizedType(ident)
} else {
Generic::Type(ident)
})
}
}
struct StaticAssertInput {
generics: Vec<Generic>,
expression: syn::Expr,
message: Option<proc_macro2::TokenStream>,
}
impl syn::parse::Parse for StaticAssertInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(StaticAssertInput {
generics: {
let generics_buf;
syn::parenthesized!(generics_buf in input);
generics_buf.parse_terminated(Generic::parse, syn::Token![,])?.into_iter().collect()
},
expression: input.parse()?,
message: if input.parse::<syn::Token![=>]>().is_ok() { Some(input.parse()?) } else { None },
})
}
}
/// The main use-case for this crate.\
/// Macro for asserting statements at compile-time, with the possibility of passing in generics as well.
/// Refer to to the crate-level documentation for more information.
#[proc_macro]
pub fn static_assert(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let StaticAssertInput { generics, expression, message } = syn::parse_macro_input!(input as StaticAssertInput);
let generic_definitions: proc_macro2::TokenStream = generics.iter().map(Generic::definition).collect();
let generic_placement: proc_macro2::TokenStream = generics.iter().map(Generic::placement).collect();
let generic_placement_types: Vec<proc_macro2::TokenStream> = generics.iter().filter_map(Generic::placement_type).collect();
quote::quote! {
_ = {
struct Assert<#generic_definitions>(#(core::marker::PhantomData<#generic_placement_types>),*);
impl<#generic_definitions> Assert<#generic_placement> {
#[allow(unused)]
const CHECK: () = if !(#expression) { panic!(#message) };
}
Assert::<#generic_placement>::CHECK
}
}.into()
}
/// Experimental macro that forces variables of a certin type to not have their destructor run, using the same methods as `static_assert!`.\
/// Its primary use case is being able to drop objects that require updating other structures.
/// It also has some serious drawbacks, meaning that you should likely refrain from using it in any serious project.\
///
/// # Example:
///
/// Consider a situation like this, where multiple allocators may be present at a time:\
///
/// ```
/// struct Allocator {
/// ...
/// }
///
/// impl Allocator {
/// pub fn alloc<T>(&mut self) -> Allocation<T> {
/// todo!()
/// }
///
/// pub fn free<T>(&mut self, allocation: Allocation<T>) {
/// // ...
/// }
/// }
///
/// struct Allocation<T> {
/// ptr_to_allocation: *const T
/// }
/// ```
///
/// Idealy, when the `Allocation` runs out of scope, it would be freed by the same `Allocator` that allocated it.
/// However, implementing such `Drop` functionality would require the allocation to also hold some kind of reference to said `Allocator`.
/// This would double its size and may become an issue if `Allocation` appears often.
/// Still, if the programmer forgets to free the `Allocation` a memory leak would take place.
///
/// Using `explicitly_drop!` would give a compile-time error if `Allocation`'s `drop` method appears anywhere in the code:
///
/// ```
/// impl<T> Drop for Allocation<T> {
/// explicitly_drop!(T => "Allocation must be freed explicitly!");
/// }
/// ```
///
/// The `free` method would have to make sure that `Allocation`'s `drop` method doesn't appear either.
///
/// ```
/// pub fn free<T>(&mut self, allocation: Allocation<T>) {
/// let allocation = std::mem::ManuallyDrop::new(allocation);
/// // ...
/// }
/// ```
///
/// Now if someone forgets to `free` an `Allocation`, the compiler will give an error
/// (just like `static_assert!`, this isn't caught by `cargo check`, and a full build is needed instead):
///
/// ```
/// fn foo(allocator: Allocator) {
/// let allocation: Allocation::<Whatever> = allocator.alloc();
/// // ... allocation is not freed
/// // allocation's drop method appears
/// }
///
/// foo(my_allocator);
///
/// // error[E0080]: evaluation of `<Allocation<T> as std::ops::Drop>::drop::Assert::<Whatever>::MANUAL_DROP` failed
/// // |
/// // | explicitly_drop!(T => "Allocation must be freed explicitly!");
/// // | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Allocation must be freed explicitly!'
/// // |
/// //
/// // note: the above error was encountered while instantiating `fn <Allocation<Whatever> as std::ops::Drop>::drop`
/// // |
/// // | pub unsafe fn drop_in_place<T: ?Sized>(to_drop: *mut T) {
/// // | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// ```
///
/// `free` (or prevent its `drop` method from appearing in any way) and the error dissapears:
///
/// ```
/// fn foo(allocator: Allocator) {
/// let allocation: Allocation::<Whatever> = allocator.alloc();
/// // ... do something
/// allocator.free(allocation);
/// }
///
/// foo(my_allocator);
///
/// // compiles just fine
/// ```
///
///
///
/// # Drawbacks
///
/// To prevent constant evaluation from always happening, the functionality is dependant on at least one generic (be it type or const)
/// from the type it's implemented in, so if the type in question doesn't have that the macro won't work:
///
/// ```
/// impl<T> Drop for Foo<T> {
/// explicitly_drop!(T => "Dependant on type generic");
/// }
///
/// impl<T: ?Sized> Drop for Foo<T> {
/// explicitly_drop!(T? => "If the type is unsized it needs special syntax");
/// }
///
/// impl<const C: u8> Drop for Foo<{C}> {
/// explicitly_drop!(const C: u8 => "Dependant on const generic (specifying the type is needed)");
/// }
///
/// impl<const C: u8, const D: u16, T, U, V> Drop for Foo<{C}, {D}, T, U, V> {
/// explicitly_drop!(const C: u8 => "Just one is needed, even if the type has more.");
/// }
/// ```
///
/// Using a lifetime as a generic doesn't work.
///
/// Secondly, the drop method might appear even when it doesn't seem it should at first glance.
/// For example, if a panic ever occours, all variables in scope, including those that need to be `explicitly_drop`ped, have their `drop`
/// method run, so even if the panic never occours at runtime, the simple appearance of `drop` will still cause a compile-time error:
///
/// ```
/// fn bar(allocator: Allocator) {
/// let allocation: Allocation::<Whatever> = allocator.alloc();
/// if rand::random::<usize>() == 27 {
/// panic!();
/// }
/// allocator.free(allocation);
/// }
///
/// foo(my_allocator);
///
/// // ... 'Allocation must be freed explicitly!' ...
/// ```
///
/// A huge amount of functionality in rust can result in panics.
/// Even if explicit `panic!`, `todo!`, or `unwrap`s are avoided, these operations, and many more, can also panic:
/// - Indexing into a container without bounds checking.
/// - Basically every unchecked heap allocation.
/// - Any mathematical operation that can over/underflow (on a debug build).
/// - Possible division or modulo operation by 0.
///
/// This method also assumes that rust optimises out, and as such doesn't attempt to evaluate
/// constants if the method they are in isn't use, which might not even always be the case.
#[proc_macro]
pub fn explicitly_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
struct ExplicitlyDropInput {
generic: Generic,
message: Option<proc_macro2::TokenStream>,
}
impl syn::parse::Parse for ExplicitlyDropInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(ExplicitlyDropInput {
generic: input.parse()?,
message: if input.parse::<syn::Token![=>]>().is_ok() { Some(input.parse()?) } else { None },
})
}
}
let ExplicitlyDropInput { generic, message } = syn::parse_macro_input!(input as ExplicitlyDropInput);
let generic_definition = generic.definition();
let generic_placement = generic.placement();
let phantomdatas = generic.placement_type()
.map(|x| quote::quote! { (core::marker::PhantomData<#x>) });
quote::quote! {
fn drop(&mut self) {
_ = {
struct Assert<#generic_definition>#phantomdatas;
impl<#generic_definition> Assert<#generic_placement> {
const MANUAL_DROP: () = panic!(#message);
}
Assert::<#generic_placement>::MANUAL_DROP
};
}
}.into()
}