Generate Enum Variant with associated values in Rust Analyzer
I’ve continued contributing to Rust Analyzer, and recently implemented a new feature that I’m exited to share with y’all: the “Generate Enum Variant” assist now supports associated values!
TL;DR:
Generate Enum Variant used to ignore associated tuples, and wasn’t available at all if an associated record was present:
Now it supports both associated tuples and records:
Do What I Mean
Rust analyzer has a category of assists that “generate” boilerplate code. These are great! Not having to type things by hand and instead having my IDE just “do what I mean” is a major speedup. Or, perhaps more accurately, 10,000 tiny speedups.
I find myself using these assists fairly often, especially when I’m doing TDD. A common suggestion for doing Test Driven Development is “write the API you wish you had”. The next step is usually “make it compile”, which is where “generate” style assists come in handy.
Say you’re written some new code, and it’s all red and squiggly. Hitting one or more “Generate…” assists will solve your syntactical sadness and set you up to focus on “getting to green”:
enum DomainModel {}
#[test]
fn the_system_produces_value() {
let value = business_logic(DomainModel::UseCase);
assert_eq!(9000, value);
}
Running “Generate Enum Variant” on UseCase
updates the DomainModel
:
enum DomainModel {
UseCase,
}
And "Generate Function" spits out:
fn business_logic(use_case: DomainModel) -> i32 {
todo!()
}
Unfortunately, we can’t (yet?) generate function implementations, so you’re still on the hook to make your code, you know, actually work 😉.
Upgrades
One of the most powerful features of Enums in rust is their ability to carry “associated values”. This is how both Option
s and Result
s work, and is a fixture of every rust codebase I’ve encountered. It’s also the feature I most long for when working in a language that doesn’t have it cough typescript cough.
When the “Generate Enum Variant” assist was originally implemented by @fasterthanlime and @maartenflippo, it didn’t support this feature. It would ignore any associated data entirely at the call site, and if you tried to write an enum variant with an associated “record”, the assist wasn’t even available 😢. No worries, gotta start somewhere! Even without this functionality, “Generate Enum Variant” has saved me time and typing!
Three cheers for incrementalism!
Now, when we’re presented with a new requirement:
#[test]
fn the_new_requirement() {
let value = business_logic(DomainModel::NewRequirement {
with: "associated values".to_string(),
like_this: true,
});
assert_eq!(9001, value);
}
"Generate Enum Variant" has got our back:
enum DomainModel {
UseCase,
NewRequirement { with: String, like_this: bool },
}
Making it happen
Here’s how it all went down:
- First, I implemented an
add_variant
method forast::VariantList
, keeping the minutia of commas, indentation, and newlines nicely encapsulated. - Next, I converted the assist from using the “raw text manipulation” style of edits to use the new
add_variant
method. - From there, it was two pretty straightforward steps to support Tuple fields and Record fields.
- Finally, @Veykril made me realize there were whole categories of
Path
s that I’d totally failed to consider, and reminded me that rust is full of patterns 🤯! I guess we know what the next incremental improvement will be!
I think my favorite part of this style of assist is how nicely they compose together. From a single call site, we can generate a variant with associated data, convert that data to a named struct, generate a From
impl, and is_
, as_
or try_into
methods. Magic!
In conclusion, don’t write code when the computer can write it for you.