Skip to main content

Module case

Module case 

Source
Expand description

Compile-time case conversion for keys and variant names.

A declaration may name a case rule, and every key it does not spell out explicitly is then converted from the Rust name during const evaluation. macro_rules! cannot manipulate a string, but a const fn can, and the rest of the machinery already takes a computed key: Keys::KEYS is an ordinary const expression and KeyMap::build runs in const context.

§The rule

Conversion is not “snake to camel”. A name is split into words and the words are respelled, which is what lets one rule serve both a field name (byte_offset) and a variant name (ByteOffset) without being told which it is looking at. A word begins:

  • after one or more _, which are separators and are never emitted;
  • at a capital that follows a lower-case letter or a digit, so byteOffset splits as byte + Offset;
  • at the last capital of a run that is followed by a lower-case letter, so HTTPUrl splits as HTTP + Url rather than at every capital.

Two consequences are worth stating outright, because they are the ones that surprise people:

A leading or trailing _ is dropped. In Rust those are the “unused” marker and the keyword escape, so type_ converts to type and _scratch to scratch. Neither underscore is part of the name the wire knows.

A run of capitals loses its capitals. HTTPUrl under "camelCase" is httpUrl, not hTTPUrl and not httpURL, because the rule respells whole words. A format that really wants httpURL should say so with an explicit "httpURL" => http_url, which is the escape hatch for every name the rule reads differently than you do.

A byte above ASCII passes through untouched, since this rule has no case for it, and a non-ASCII identifier therefore keeps its spelling and stays valid UTF-8. It never begins a word on its own account, only a capital and a separator do that, but it does end one: caféBar splits as café + Bar, so the B keeps its case rather than being run into the word before it.

Two names whose converted keys collide are a compile error, from the duplicate check KeyMap::build already performs.

A raw identifier loses its r# before the rule sees it, so a field written r#type has the key type, and a rule respells type. The prefix is how Rust spells a name that collides with a keyword, not part of the name the wire knows.

§Coming from serde

The eight spellings are serde’s. The rule behind them is not, and three differences will change your bytes:

  • "lowercase" and "UPPERCASE" keep serde’s underscores and these do not. Serde’s field rules take the name to be snake_case already, so lowercase is the identity and UPPERCASE is to_ascii_uppercase: byte_offset stays byte_offset and becomes BYTE_OFFSET. Here they mean what they say, and give byteoffset and BYTEOFFSET.
  • Acronyms in a variant name. Serde’s variant rules break at every capital, so HTTPProxy under "snake_case" is h_t_t_p_proxy. Here it is http_proxy.
  • Serde has two rules and this has one. Which of serde’s applies depends on whether the name is a field or a variant, so a field that is not snake_case, or a variant that is not PascalCase, is converted by a rule that was not written for it. One rule over words has no such seam.

Everything else agrees: for a snake_case field and an acronym-free PascalCase variant, the other six rules land on the string serde lands on.

Enums§

Style
How a converted name is spelled.

Functions§

as_str
The key macros’ const from_utf8, which is how a computed key reaches the &'static str the rest of the crate wants without an unsafe block.
cased
name converted to style.
cased_len
The length of name converted to style.
style
The Style a declaration named.
unraw
A field or variant name with the r# of a raw identifier removed.