Macro const_panic::impl_panicfmt
source · [−]macro_rules! impl_panicfmt {
(
$kind:tt $typename:ident < $($rem:tt)*
) => { ... };
(
$kind:tt $typename:ident $($rem:tt)*
) => { ... };
}
non_basic
only.Expand description
Implements the PanicFmt
trait and the to_panicvals
method it requires.
This macro roughly takes a type definition and a (conditionally required) list of impls, for an example demonstrating all the parts of the syntax look here.
For a derive macro alternative, there’s the PanicFmt
derive,
which requires the "derive"
feature(disabled by default).
Limitations
Type parameters
Types with type parameters can’t be generically formatted, which has two workarounds.
The first workaround is marking a type parameter as ignored with an ignore
prefix,
if the type parameter(s) are only used in marker types (eg: PhantomData
).
example of this workaround
The second workaround is to implement panic formatting with concrete type arguments,
using trailing (impl Foo<Bar>)
s.
example of this workaround
This limitation is caused by:
- the lack of trait bound support in stable const fns.
- the need to have a concrete type argument
Const parameters
Const parameters must not affect the value of the PanicFmt::PV_COUNT
of this type,
since the const parameter must be replaceable with a concrete value.
Note that arrays have a PV_COUNT
of 1
for all lengths.
Concrete Self
type for PanicFmt::PV_COUNT
The to_panicvals
method that this macro generates roughly returns a
[PanicVal<'_>; <Self as PanicFmt>::PV_COUNT]
Because of limitations in stable const generics,
the generic arguments of Self
in the above code must be replaced with concrete arguments,
requiring:
- Lifetime arguments to be replaced with
'_
- Type arguments to be replaced with concrete types (usually
()
) - Const arguments to be replaced with concrete values (usually the default value for the type)
Examples
Struct formatting
use const_panic::{ArrayString, FmtArg, impl_panicfmt};
fn main(){
let foo = Foo {
x: &[3, 5, 8, 13],
y: 21,
z: Bar(false, true),
};
assert_eq!(
ArrayString::<100>::from_panicvals(&foo.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Foo { x: [3, 5, 8, 13], y: 21, z: Bar(false, true) }",
);
assert_eq!(
ArrayString::<200>::from_panicvals(&foo.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
concat!(
"Foo {\n",
" x: [\n",
" 3,\n",
" 5,\n",
" 8,\n",
" 13,\n",
" ],\n",
" y: 21,\n",
" z: Bar(\n",
" false,\n",
" true,\n",
" ),\n",
"}",
),
);
}
struct Foo<'a> {
x: &'a [u8],
y: u8,
z: Bar,
}
// Implementing `PanicFmt` and the `to_panicvals` method for `Foo<'a>`
impl_panicfmt!{
struct Foo<'a> {
x: &'a [u8],
y: u8,
z: Bar,
}
}
struct Bar(bool, bool);
impl_panicfmt!{
struct Bar(bool, bool);
}
Enum Formatting
use const_panic::{ArrayString, FmtArg, impl_panicfmt};
fn main() {
let up: Qux<u8> = Qux::Up;
// Debug formatting the Up variant
assert_eq!(
ArrayString::<100>::from_panicvals(&up.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Up",
);
let down: Qux<u16> = Qux::Down { x: 21, y: 34, z: 55 };
// Debug formatting the Down variant
assert_eq!(
ArrayString::<100>::from_panicvals(&down.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Down { x: 21, y: 34, z: 55 }",
);
// Alternate-Debug formatting the Down variant
assert_eq!(
ArrayString::<100>::from_panicvals(&down.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
concat!(
"Down {\n",
" x: 21,\n",
" y: 34,\n",
" z: 55,\n",
"}",
)
);
let left: Qux<u32> = Qux::Left(89);
// Debug formatting the Left variant
assert_eq!(
ArrayString::<100>::from_panicvals(&left.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Left(89)",
);
// Alternate-Debug formatting the Left variant
assert_eq!(
ArrayString::<100>::from_panicvals(&left.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
concat!(
"Left(\n",
" 89,\n",
")",
)
);
}
enum Qux<T> {
Up,
Down { x: T, y: T, z: T },
Left(u64),
}
// Because of limitations of stable const evaluation,
// `Qux` can't generically implement panic formatting,
// so this macro invocation implements panic formatting for these specifically:
// - `Qux<u8>`
// - `Qux<u16>`
// - `Qux<u32>`
impl_panicfmt!{
enum Qux<T> {
Up,
Down { x: T, y: T, z: T },
Left(u64),
}
(impl Qux<u8>)
(impl Qux<u16>)
(impl Qux<u32>)
}
Type parameters
This example demonstrates support for types with type parameters.
use const_panic::{ArrayString, FmtArg, impl_panicfmt};
use std::marker::PhantomData;
{
let with_int = Foo::<&str, u8> {
value: 100u8,
_marker: PhantomData,
};
assert_eq!(
ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Foo { value: 100, _marker: PhantomData }",
);
}
{
let with_str = Foo::<bool, &str> {
value: "hello",
_marker: PhantomData,
};
assert_eq!(
ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(),
r#"Foo { value: "hello", _marker: PhantomData }"#,
);
}
#[derive(Debug)]
pub struct Foo<A, B> {
value: B,
_marker: PhantomData<A>,
}
impl_panicfmt!{
// `ignore` here tells the macro that this type parameter is not formatted.
struct Foo<ignore A, B> {
value: B,
_marker: PhantomData<A>,
}
// Because type parameters can't be generically formatted,
// you need to list impls with concrete `B` type arguments.
//
// the generic parameters to the impl block go inside `[]`
(impl[A] Foo<A, u8>)
// the bounds in where clauses also go inside `[]`
(impl[A] Foo<A, &str> where[A: 'static])
}
Phantom Type parameters
This example demonstrates how type parameters can be ignored.
use const_panic::{ArrayString, FmtArg, impl_panicfmt};
use std::marker::PhantomData;
{
let with_int: Foo<u8, bool, 100> = Foo{
value: 5,
_marker: PhantomData,
};
assert_eq!(
ArrayString::<100>::from_panicvals(&with_int.to_panicvals(FmtArg::DEBUG)).unwrap(),
"Foo { value: 5, _marker: PhantomData }",
);
}
{
let with_str: Foo<str, char, 200> = Foo {
value: 8,
_marker: PhantomData,
};
assert_eq!(
ArrayString::<100>::from_panicvals(&with_str.to_panicvals(FmtArg::DEBUG)).unwrap(),
r#"Foo { value: 8, _marker: PhantomData }"#,
);
}
#[derive(Debug)]
pub struct Foo<A: ?Sized, B, const X: u32> {
value: u32,
_marker: PhantomData<(PhantomData<A>, B)>,
}
impl_panicfmt!{
// `ignore` here tells the macro that this type parameter is not formatted.
//
// `ignore(u8)` tells the macro to use `u8` as the `B` type parameter for
// `<Foo<....> as PanicFmt>::PV_COUNT` in the generated `to_panicvals` method.
struct Foo<ignore A, ignore(u8) B, const X: u32>
// bounds must be written in the where clause, like this:
where[ A: ?Sized ]
{
value: u32,
_marker: PhantomData<(PhantomData<A>, B)>,
}
}
All the syntax
impl_panicfmt!{
struct Foo<
'a,
'b,
// For type parameters that aren't formatted.
// Removes the `PanicFmt` bound on this type parameter
// and uses `()` as the type argument for this to get
// `<Foo<....> as PanicFmt>::PV_COUNT` in the generated `to_panicvals` methods.
ignore C,
// same as `C`, using `u8` instead of `()`
ignore(u8) D,
// un-`ignore`d type parameters must be replaced with concrete types in the impls below
E,
const X: u32,
>
// bounds must be written in the where clause, like this:
where[ C: ?Sized, D: ?Sized, E: ?Sized ]
{
_lifetimes: PhantomData<(&'a (), &'b ())>,
_marker: PhantomData<(PhantomData<C>, PhantomData<D>, PhantomData<E>)>,
}
// The impls for this type, this is required for types with un-`ignore`d type parameters.
// Otherwise, a generic impl is automatically generated.
(
impl['a, 'b, D, E: ?Sized, const X: u32] Foo<'a, 'b, D, E, u32, X>
where[D: ?Sized]
)
(
impl['a, 'b, D, E: ?Sized, const X: u32] Foo<'a, 'b, D, E, &str, X>
where[D: ?Sized]
)
}