Hi, how do I do this?
I'm using compile time reflection in Nightly to perform checks. Once an error is detected, I want to generate a compile time error with a custom error message that includes the line number information
I tried the compile_error! macro, but it gets executed at the library level rather than when the library code is actually invoked
I tried panic! executed during compile time eval, but the error message isn't clean because it includes a lot of unnecessary stack traces for my use case. Is there a way to make the custom error message from a panic clean?
I tried combining traits + #[diagnostic::on_unimplemented] + const generic expressions to bridge the compile time reflection, traits, and generic functions. This allowed me to customize the error message according to the use case and remove complex trait technical detail messages that aren't helpful in my case because the error can't be fixed by implementing a trait (I'm preventing raw pointer returns, so the only fix is to stop returning pointers). I wanted to remove these complex messages to keep it clean (some parts still couldn't be hidden), but it was still cleaner than panic messages. The issue is that generic const expressions throw an incomplete feature warning. Previously it successfully detected pointers, but after a while I re ran it without changing any logic in the pointer detection code, and it suddenly failed, it suddently detects any types as raw pointers. When I switched back to the panic method, it worked normally again, so the generic const expression was what suddenly failed
Is there a way to generate a compile time error with a custom message that is clean and allows removing unnecessary messages?
I think the solution to your question is a const panic. It usually provides pretty good error messages:
fn f<T>(_t: T) {
const { assert!(size_of::<T>() >= 4, "T is too small. Foo bar"); }
}
fn main() {
f(0u64);
f(0u32);
f(true); // removing this line gets rid of the compile error.
}
Maybe you can expand a bit on why this didn't work for you?
A const panic worked previously, but as I explained earlier, it generates a lot of error messages that I don't need in this case
This is the error message from const panic
Compiling alias v0.1.0 (/root/alias) error[E0080]: evaluation panicked: return type contains raw pointer
--> aliasing_guard/src/lib.rs:281:17 |
281 | const { assert_no_pointer::<R>() }; | ^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `aliasing_guard::AliasingGuard::<'_, std::string::String>::with_mutable_pointer::<*mut std::string::String, {closure@aliasing_guard/src/lib.rs:346:41: 346:47}>::{constant#0}` failed inside this call
|
note: inside `aliasing_guard::assert_no_pointer::<*mut String>`
--> /root/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic.rs:62:9
|
62 | ... $crate::panicking::panic_fmt($crate::const_format_args!($($t)+));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the failure occurred here
|
::: aliasing_guard/src/lib.rs:318:5
|
318 | assert!(a == false, "return type contains raw pointer");
| ------------------------------------------------------- in this macro invocation
note: erroneous constant encountered
--> aliasing_guard/src/lib.rs:281:9
|
281 | const { assert_no_pointer::<R>() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: the above error was encountered while instantiating `fn AliasingGuard::<'_, ...>::with_mutable_pointer::<..., ...>`
--> src/main.rs:12:9
|
12 | let b = guard!(guard *mut a {
| _________^
13 | | a
14 | | });
| |__^
|
= note: the full name for the type has been written to '/root/alias/target/debug/deps/alias-16e11f3241a63190.long-type-3392425769687388814.txt'
= note: consider using `--verbose` to print the full type name to the console
= note: this note originates in the macro `guard` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0080`.
warning: `alias` (bin "alias") generated 1 warning
error: could not compile `alias` (bin "alias") due to 1 previous error; 1 warning emitted
[dev@localhost alias]#
This is the error message from diagnostic::on_unimplemented
Compiling alias v0.1.0 (/root/alias)
error[E0277]: return type can not contain raw pointer
--> src/main.rs:12:9
|
12 | let b = guard!(guard *const a {
| _________^
13 | | a
14 | | });
| |__^ returning type that contains raw pointer is forbidden here
|
= help: the trait `NoPointerAllowed` is not implemented for `NoPointerCheck<aliasing_guard::::{impl#1}::with_immutable_pointer::{constant#0}>`
= note: this trait system is designed to prevent returning raw pointer
note: required by a bound in `aliasing_guard::AliasingGuard::<'a, T>::with_immutable_pointer`
--> aliasing_guard/src/lib.rs:255:61
|
253 | ...fn with_immutable_pointer<R>(&self, f: impl FnOnce(*const T) -> R) -> R
| ---------------------- required by a bound in this associated function
254 | ...e
255 | ...interCheck<{ !type_has_pointer(Type::of::<R>()) }>: NoPointerAllowed
| ^^^^^^^^^^^^^^^^ required by this bound in `AliasingGuard::<'a, T>::with_immutable_pointer`
= note: this error originates in the macro `guard` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.
error: could not compile `alias` (bin "alias") due to 1 previous error
[dev@localhost alias]#
If possible, I want the error message to be clean like this, removing unnecessary text that isn't needed in this case so it's easier to read and leaves no potential for confusion
Compiling alias v0.1.0 (/dev/alias)
error[E0277]: return type can not contain raw pointer
--> src/main.rs:12:9
|
12 | let b = guard!(guard *const a {
| _________^
13 | | a
14 | | });
| |__^ returning type that contains raw pointer is forbidden here
|
= help: return type must be a type that doesn't contain raw pointer
= note: the guard is designed to prevent returning raw pointer
error: could not compile `alias` (bin "alias") due to 1 previous error
and
are somewhat incompatible concepts: invocation can only happen at runtime, which is way past compile time by definition alone. You may want to experiment with #[cfg(debug_assertions)] guards instead, which will validate all the invariants the first time you reach them.
Example
macro_rules! error {
($str:literal, $($tt:tt)+) => {
eprintln!(concat!(line!(), ":", column!(), ": ", $str), $($tt)+);
std::process::exit(101);
};
}
fn f<T>(_t: T) {
#[cfg(debug_assertions)]
if size_of::<T>() < 4 {
error!("type `{}` is too small", std::any::type_name::<T>());
};
}
fn main() {
f(0u64);
f(0u32);
f(true);
}
// output:
// -
// 11:9: type `bool` is too small
Edit: with regards to
you can also override the default panic handler; yet again, that is only an option at runtime.