From fac0109b96743012ea6edf4efe9fb820732b341e Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Sun, 13 Aug 2023 20:54:21 +0100 Subject: [PATCH] Document custom error types in README (#125) Also fix up heading structures --- README.md | 139 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 5442ec3..0191472 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -num_enum -======== +# num_enum Procedural macros to make inter-operation between primitives and enums easier. This crate is no_std compatible. @@ -8,8 +7,7 @@ This crate is no_std compatible. [![Documentation](https://docs.rs/num_enum/badge.svg)](https://docs.rs/num_enum) [![Build Status](https://travis-ci.org/illicitonion/num_enum.svg?branch=master)](https://travis-ci.org/illicitonion/num_enum) -Turning an enum into a primitive --------------------------------- +## Turning an enum into a primitive ```rust use num_enum::IntoPrimitive; @@ -29,8 +27,7 @@ fn main() { `num_enum`'s `IntoPrimitive` is more type-safe than using `as`, because `as` will silently truncate - `num_enum` only derives `From` for exactly the discriminant type of the enum. -Attempting to turn a primitive into an enum with try_from ----------------------------------------------- +## Attempting to turn a primitive into an enum with try_from ```rust use num_enum::TryFromPrimitive; @@ -55,8 +52,7 @@ fn main() { } ``` -Variant alternatives ---------------- +### Variant alternatives Sometimes a single enum variant might be representable by multiple numeric values. @@ -122,43 +118,34 @@ fn main() { } ``` -Default variant ---------------- +### Custom error types -Sometimes it is desirable to have an `Other` variant in an enum that acts as a kind of a wildcard matching all the value not yet covered by other variants. - -The `#[num_enum(default)]` attribute (or the stdlib `#[default]` attribute) allows you to mark variant as the default. +`TryFromPrimitive` by default will use `num_enum::TryFromPrimitiveError` as its `Error` type. -(The behavior of `IntoPrimitive` is unaffected by this attribute, it will always return the canonical value.) +If you want to use a different type, you can use an annotation for this: ```rust -use num_enum::FromPrimitive; -use std::convert::TryFrom; +use num_enum::TryFromPrimitive; -#[derive(Debug, Eq, PartialEq, FromPrimitive)] +#[derive(Debug, Eq, PartialEq, TryFromPrimitive)] +#[num_enum(error_type(name = CustomError, constructor = CustomError::new))] #[repr(u8)] -enum Number { - Zero = 0, - #[num_enum(default)] - NonZero = 1, +enum FirstNumber { + Zero, + One, + Two, } -fn main() { - let zero = Number::from(0u8); - assert_eq!(zero, Number::Zero); - - let one = Number::from(1u8); - assert_eq!(one, Number::NonZero); +struct CustomError {} - let two = Number::from(2u8); - assert_eq!(two, Number::NonZero); +impl CustomError { + fn new(value: u8) -> CustomError { + CustomError {} + } } ``` -Only `FromPrimitive` pays attention to `default` attributes, `TryFromPrimitive` ignores them. - -Safely turning a primitive into an exhaustive enum with from_primitive -------------------------------------------------------------- +## Safely turning a primitive into an exhaustive enum with from_primitive If your enum has all possible primitive values covered, you can derive `FromPrimitive` for it (which auto-implement stdlib's `From`): @@ -191,8 +178,41 @@ fn main() { } ``` -Catch-all variant ------------------ +### Default variant + +Sometimes it is desirable to have an `Other` variant in an enum that acts as a kind of a wildcard matching all the value not yet covered by other variants. + +The `#[num_enum(default)]` attribute (or the stdlib `#[default]` attribute) allows you to mark variant as the default. + +(The behavior of `IntoPrimitive` is unaffected by this attribute, it will always return the canonical value.) + +```rust +use num_enum::FromPrimitive; +use std::convert::TryFrom; + +#[derive(Debug, Eq, PartialEq, FromPrimitive)] +#[repr(u8)] +enum Number { + Zero = 0, + #[num_enum(default)] + NonZero = 1, +} + +fn main() { + let zero = Number::from(0u8); + assert_eq!(zero, Number::Zero); + + let one = Number::from(1u8); + assert_eq!(one, Number::NonZero); + + let two = Number::from(2u8); + assert_eq!(two, Number::NonZero); +} +``` + +Only `FromPrimitive` pays attention to `default` attributes, `TryFromPrimitive` ignores them. + +### Catch-all variant Sometimes it is desirable to have an `Other` variant which holds the otherwise un-matched value as a field. @@ -224,34 +244,13 @@ fn main() { As this is naturally exhaustive, this is only supported for `FromPrimitive`, not also `TryFromPrimitive`. -Unsafely turning a primitive into an enum with unchecked_transmute_from ------------------------------------------------------------------------ +## Unsafely turning a primitive into an enum with unchecked_transmute_from If you're really certain a conversion will succeed (and have not made use of `#[num_enum(default)]` or `#[num_enum(alternatives = [..])]` for any of its variants), and want to avoid a small amount of overhead, you can use unsafe code to do this conversion. Unless you have data showing that the match statement generated in the `try_from` above is a bottleneck for you, you should avoid doing this, as the unsafe code has potential to cause serious memory issues in your program. -Note that this derive ignores any `default`, `catch_all`, and `alternatives` attributes on the enum. -If you need support for conversions from these values, you should use `TryFromPrimitive` or `FromPrimitive`. - - - This means, for instance, that the following is UB: - - ```rust,no_run - use num_enum::UnsafeFromPrimitive; - - #[derive(UnsafeFromPrimitive)] - #[repr(u8)] - enum Number { - Zero = 0, - - // Same for `#[num_enum(catch_all)]`, and `#[num_enum(alternatives = [2, ...])]` - #[num_enum(default)] - One = 1, - } - let _undefined_behavior = unsafe { Number::unchecked_transmute_from(2) }; - ``` - ```rust use num_enum::UnsafeFromPrimitive; @@ -278,8 +277,27 @@ unsafe fn undefined_behavior() { } ``` -Optional features ------------------ +Note that this derive ignores any `default`, `catch_all`, and `alternatives` attributes on the enum. +If you need support for conversions from these values, you should use `TryFromPrimitive` or `FromPrimitive`. + +This means, for instance, that the following is undefined behaviour: + +```rust,no_run +use num_enum::UnsafeFromPrimitive; + +#[derive(UnsafeFromPrimitive)] +#[repr(u8)] +enum Number { + Zero = 0, + + // Same for `#[num_enum(catch_all)]`, and `#[num_enum(alternatives = [2, ...])]` + #[num_enum(default)] + One = 1, +} +let _undefined_behavior = unsafe { Number::unchecked_transmute_from(2) }; +``` + +## Optional features Some enum values may be composed of complex expressions, for example: @@ -293,7 +311,6 @@ enum Number { To cut down on compile time, these are not supported by default, but if you enable the `complex-expressions` feature of your dependency on `num_enum`, these should start working. -License -------- +## License num_enum may be used under your choice of the BSD 3-clause, Apache 2, or MIT license.