cstr_enum/
lib.rs

1#![warn(missing_docs)]
2//! A crate for defining C-style string enums.
3//!
4//! C APIs sometimes require string constants.  One could define a bunch of `&CStr` constants using the
5//! [`constr_cstr`](https://docs.rs/const-cstr/) crate, but this becomes unergonomic with a large number of constants.
6//! It also does not allow the type checking Rust's enums provide.
7//!
8//! This crate provides two traits for converting between to and from `&CStr`: `AsCStr` and `FromCStr`.  It also provides
9//! derive macros for implementing these traits on enums.  The implementations provided
10//! by the derive macros perform no allocations, using only static `[u8]` buffers.
11//!
12//! ```
13//! use cstr_enum::*;
14//! use std::ffi::CStr;
15//! use std::os::raw::c_char;
16//!
17//! #[derive(Debug, Eq, PartialEq, FromCStr, AsCStr)]
18//! enum Constants {
19//!   Apple,
20//!   Bacon,
21//!   Cat = 1337, // user discriminants supported
22//! }
23//!
24//! assert_eq!(Constants::Apple.as_cstr().to_bytes_with_nul(), b"Apple\0");
25//!
26//! let returned_from_c_api = CStr::from_bytes_with_nul(b"Cat\0").unwrap();
27//! assert_eq!(Constants::from_cstr(returned_from_c_api), Ok(Constants::Cat));
28//!
29//! let returned_from_c_api = CStr::from_bytes_with_nul(b"unknown\0").unwrap();
30//! assert_eq!(
31//!   Constants::from_cstr(returned_from_c_api),
32//!   Err("unexpected string while parsing for Constants variant")
33//! );
34//! ```
35//! Both derive macros allow the re-naming of enum variants using the `cstr(name="string literal")` attribute on enum variants.
36//! ```
37//! # use cstr_enum::*;
38//! #
39//! #[derive(Debug, Eq, PartialEq, FromCStr, AsCStr)]
40//! enum Constants {
41//!   #[cstr(name="pork")]
42//!   Bacon,
43//! }
44//!
45//! assert_eq!(Constants::Bacon.as_cstr().to_bytes_with_nul(), b"pork\0");
46//! ```
47//! Nul bytes in the supplied string will be rejected at compile time.
48//! ```compile_fail
49//! # use cstr_enum::*;
50//! #
51//! #[derive(Debug, Eq, PartialEq, FromCStr, AsCStr)]
52//! enum Constants {
53//!   #[cstr(name="p\0rk")]
54//!   Bacon,
55//! }
56//! ```
57//! ```text
58//! error: string cannot contain nul bytes
59//!   |   #[cstr(name="p\0rk")]
60//!   |               ^^^^^^^
61//! ```
62//! When deriving `AsCStr`, enum variants may contain fields:
63//! ```
64//! # use cstr_enum::*;
65//! #
66//! #[derive(Debug, AsCStr)]
67//! enum Constants {
68//!   Foo{ bar: u8 },
69//!   Baz(u8, u16)
70//! }
71//! assert_eq!(Constants::Foo{ bar: 0 }.as_cstr().to_bytes_with_nul(), b"Foo\0");
72//! assert_eq!(Constants::Baz(0,0).as_cstr().to_bytes_with_nul(), b"Baz\0");
73//! ```
74//! This is not the case deriving `FromCStr`:
75//! ```compile_fail
76//! # use cstr_enum::*;
77//! #
78//! #[derive(Debug, FromCStr)]
79//! enum Constants {
80//!   Foo{ bar: u8 },
81//!   Baz(u8, u16)
82//! }
83//! ```
84//! ```text
85//! error: variant cannot have fields
86//!   |   Foo{ bar: u8 },
87//!   |   ^^^^^^^^^^^^^^
88//! ```
89//!
90//! Conversion between Rust strings ([`str`] and [`String`]) is not supported by this crate. Instead, check out
91//! the [`strum`](https://docs.rs/strum/) crate.
92use std::ffi::CStr;
93
94/// Conversion to a C-style string.
95///
96/// If using the derive macro, this will be a cheap conversion.
97pub trait AsCStr {
98  /// Represent self as a [`&CStr`](std::ffi::CStr)
99  fn as_cstr(&self) -> &CStr;
100}
101
102/// Conversion from a C-style string
103///
104/// This trait should be used the same way as [`std::str::FromStr`], although
105/// a separate `.parse()` implementation is not provided `&str`
106pub trait FromCStr {
107  /// The error type returned if parsing fails.
108  ///
109  /// If using the derive macro, this will be `&'static str`.
110  type Err : Sized;
111  /// Parse the `&CStr` for an instance of `Self`.
112  ///
113  /// If using the derive macro, this will be a `match` statement over `&'static [u8]`.
114  fn from_cstr(s: &CStr) -> Result<Self, Self::Err> where Self: Sized;
115}
116
117pub use cstr_enum_derive::*;