culit - Custom Literals in Rust
You probably know that numbers in Rust can be suffixed to specify their type, e.g. 100i32.
But did you know that syntactically any literal can have a suffix? And did you know that the suffix can be whatever you want?
This crate provides an attribute macro #[culit] for "Custom Literals". When applied to any statement, it enables using custom literals in that statement.
[]
= "0.1"
Note: culit does not have any dependencies such as syn or quote, and it is a simple mapping SourceCode -> SourceCode, so compile-speeds will be very fast.
Example
A NonZeroUsize literal that fails to compile if it is 0: 100nzusize
use culit;
use NonZeroUsize;
IDE Support
Hovering over the custom literals shows documentation for the macro that generates them. You can also do "goto definition". It's quite nice!

More Examples
Python-like f-strings: "hello {name}"f
use culit;
use Duration;
Duration literals: 100m, 2h...
use culit;
use Duration;
The possibilities are endless!
Details
#[culit] recursively replaces every literal that has a non-standard suffix with a call to the macro
at crate::custom_literal::<type>::<suffix>!(...), for example:
100kmexpands tocrate::custom_literal::int::km!("100" 10)"100"is the value10is the base (decimal)
70.8e7feetexpands tocrate::custom_literal::float::feet!("70" "8" "e7")"70"is the part before the decimal"8"is the part after the decimal"e7"is the exponent
'a'asciiexpands tocrate::custom_literal::char::ascii!('a')b'a'asciiexpands tocrate::custom_literal::byte_char::ascii!(97)"foo"barexpands tocrate::custom_literal::str::bar!("foo")b"foo"barexpands tocrate::custom_literal::byte_str::bar!(b"foo")c"foo"barexpands tocrate::custom_literal::c_str::bar!(c"foo")
Negative literals
Whatever the macros in custom_literal::float or custom_literal::int expand to needs to implement the Neg trait in order to allow using - with the custom numeric literals.
Details on negative literals
You might think that a number like -100 is a single literal, but it is not. It is 2 tokens: a punctuation , followed by a literal 100. -100km expands like this:
-is a punctuation, it is kept as-is100kmis a literal100with suffixkm. It expands tocrate::custom_literal::int::km!("100" 10)."100"is string representation of the number,10is the base (which could also be2,8or16)- The macro receives a string
"100"instead of an integer100because procedural macros cannot create integer literals that are larger thanu128, but we want to support integer literals of arbitrary size. - More importantly, interpreting the number itself without the base is a logic error, so passing a string instead of integer makes it far less likely that you'll make mistakes
-100kmoverall expands to-crate::custom_literal::int::km!("100" 10). Notice the-at the beginning, it is kept the same. Whateverkm!expands to needs to implement theNegtrait to be able to be used with the-operator.
Skeleton
Here's a skeleton for the custom_literal module which must exist at crate::custom_literal.
This module adds a new literal for every type of literal:
Nightly
You need to use #[culit] attribute everywhere you want to use these literals. On nightly, you can apply it on the module:
While this works, I wouldn't recommend it - currently rust-analyzer is unable to properly work with custom inner attributes
that modify the whole crate. For example, if you write 0nzusize which produces a compiler error, the span of the error will point to
the macro crate::custom_literal::int::nzusize but not the actual 0nzusize, which makes it very hard to debug these