`typed-ident`: Type-safe identifier validation, inspection, and mutation. (and a retrospective article!)

Hey everyone!

I'm very happy to announce that I've published my first open-source Rust crate!

I really like to express things in Rust in terms of concrete types (kind of like how one could keep paths as strings in a language, but it's nice to say specifically they are a Path). This is a similar set of types, but for dealing with programming identifiers and their formats in a type-safe way (LowerCamelIdent, UpperKebabIdent, etc).

It also has a bunch of functionality relating to inspection of identifiers, as well as mutation of them (including the common "change case" operations).

I would love feedback, questions, analysis, or if you think you have a use-case!

EDIT: Also I'm very interested to hear *criticism* as well - if you don't like it, let me know why!

EDIT: I wanted to add that this crate does not use any AI-generated code. I get that it's a lot of code, but it's been a project of a few months of effort.

Some Features

  • Unicode Correctness: Implements boundaries based on UTS #55 and UAX #31 (handles the weird stuff like non-Greek titlecase)
  • Zero-Cost Casting: Uses marker traits and the type system to allow compile-time casting between compatible identifier formats
  • Flexible Conversion: Supports "Canonical," "Decorated," and "Delimited" forms so you can preserve decorative underscores/hyphens during case conversion
  • High Performance: Competitive with (and in some cases faster than) heck and convert_case

About Me

If you're curious about me - I have a website (it's static, and the generator is also made in Rust, not open-source though)!

Thanks for reading - I hope you enjoyed taking a look at my project!

Hey Everyone - a few small updates on this project.

v0.0.2 Released!

I wasn't going to reply to this post to share this, as there's some concern that these announcement posts are kind of annoying people (topic), but since I'm here to answer a question I figure I might as well share this update.

Lots of improvements in the v0.0.2 release. I'm going to be keeping a Changelog, so you can read about them there.

A few highlights:

  • Improved performance (perf was already pretty darn good - now it's even better!).
  • Code generation to automate some of the originally manually-coded UTF-8 stuff (like Greek vs non-Greek title case).
    • I'm kind of proud of this one, I think the code generator is pretty nicely-built too - you can take a look at it here.
  • Some great improvements to the FragmentBuf API - it's a lot easier to work with now.
  • Greatly improved documentation (still not perfect, this is going to be an iterative thing).

v0.0.3 Plans!

There's already a lot planned for v0.0.3 - while the path to v0.0.2 only took a week, that was because I considered some of the improvements super important to make as soon as possible. v0.0.3 is still an important release, but it's less time pressing, so I'm probably going to take it a bit slower (maybe like a few weeks).

I was pulling 12-hour days to release v0.0.2, and I don't really like doing that on the regular...

A few highlights for the next release:

  • #3 Going to add a feature to integrate with proc-macro2 and quote crates to improve working with typed-ident from a proc-macro perspective (one of the primary use-cases of this crate).
  • #17 Improving the To* conversion traits so that they can either form string representations, or some kind of typed-ident representation (instead of just strings as they do now).
  • #23 Continued improvement for documentation - I wont stop until I'm happy with it.
  • #24 Analysis to make a formal recommendation when dealing with UTF-8 characters that have no alternative case mappings (e.g. are lowercase with no uppercase or vice-versa).

Questions & Answers

I got a question in the What's everyone working on this week (39/2026) thread. I didn't want to derail that topic, so I figured I'd answer it here.

The question was from mahadev_rust26, and said:

proc-macro2 integration sounds like a solid direction, curious what kind of ergonomics you're aiming for with typed-ident, is the goal mostly compile-time safety for identifiers, or also better error messages when something doesn't match the expected type?

This is a fair question, and why I think documentation still isn't up-to-par. Documentation should very clearly show the benefit of a crate, and I think the current documentation fails at this.

There's a few reasons one might select to use typed-ident:

  1. They want Unicode Technical Standard #55 compliant boundary detection (and thus case conversion).

To my knowledge, typed-ident is maybe the only crate that implements this standard for identifier chunking? So your options are very limited if you want this.

All case conversion is: detect boundary, normalize casing, insert delimiters where needed.

So, boundary formation impacts things like case conversion. convert_case and heck are general case conversion crates that just so happen to be used in places like proc-macros, but typed-ident is specifically targeting identifiers. So while I think it may not be a good argument for those crates to support such boundary detection algorithms, but it sounds totally reasonable for typed-ident to.

  1. They want more careful case conversion that doesn't drop decorative delimiters.

Like with (1), since typed-ident is focusing on identifiers, it's able to be a bit more specific about how the conversion process happens and what is/isn't safe. I'll recommend you read the typed_ident::fmt to understand more clearly the kinds of things you can do surrounding this.

  1. They want to mutate an identifier in a safe way that doesn't invalidate the identifier

Since typed-ident knows about things like character profiles, it's safer to use it to mutate an identifier, and fail earlier in the process than when you go to inject or use the identifier in some code. This hopefully will lead to better error messages instead of waiting until post-generation to see that something fails. (I tend to like encoding properties about data in the type system; typestate kind of stuff. you could squint at typed-ident and view it like that.)

For proc-macros maybe not the biggest deal since you'll compile-error shortly after the proc-macro runs, but this can be important if you have some other developer tool that produces code that it expects some far further part of the pipeline to actually compile.


For me, personally - I have two places I will be using this crate.

First, within proc-macros. I plan to use it primarily for case conversion, but there's a few other places as well (my next crate will be making use of this crate, for instance - it will make use of the segments method).

I hope for it to be safer for such use-cases, easier to fail early on (producing clearer error messages), and actually performs quite well (minimal runtime cost and binary cost for high correctness and quality). This is still being worked on, but I suspect in the end it'll look something like this:

fn some_function_dealing_with_a_proc_macro_ident(ident: &Ident) -> Result<...> {
    // Convert to a typed-ident of some general format (let's say camel-cased).
    let ident: Spanned<CamelIdent> = ident.into();
    
    // Maybe we want to reformat it as a snake-case thing.
    let snake: Spanned<SnakeIdent> = ident.to_lower_snake()?;

    // Maybe we want to append some common prefix on the main identifier.
    let prefixed = ident.with_prefix("Example")?;
    
    // Both of these are usable within quote:
    Ok(quote!(
        mod #snake {
            pub struct #prefixed;
        }
    ))
}

Anyway, something like that - I'm still working on it.

A safer way to work with identifiers, basically. I still recommend using convert_case instead for general purpose strings though - like if you're writing a text editor and you need such functionality or working with filenames or something.

Second, I'm going to be building some tools that take inputs that I expect to be formatted like identifiers. I want to mutate these identifiers in various ways for codegen and documentation purposes (not always targeting Rust). This is very similar to the proc-macro use case, but a little broader.

I want it to work even without being in a proc-macro for developer tooling. I have a specific place I want to use it for this, but I am not ready to share it yet. However, I think it's not hard to imagine wanting to validate the inputs of something that you expect to be "like an identifier" - and then potentially carefully mutate and work with the identifier in various ways.

I kind of want typed-ident to be the "if you need to do something non-trivial with an identifier, this is the crate for you" crate. This is why the Ident type is so built-out to support so many features, and why it has very clear and careful typing.


I hope this helps!

If there are any more questions, feel free to ask!