Two reasons most of my tooling is drifting from Python to Rust, and neither of them is fashion.
One: deployment. A Rust tool compiles to a single static binary. No interpreter on the target, no pip install halfway through an engagement, no "works on my machine." You drop one file and it runs. Anywhere I'd rather not leave a Python install and a trail of packages behind, that matters.
Two: the type system catches footguns at compile time. The pattern I lean on hardest is making invalid states unrepresentable:
struct RawInput(String);
struct ValidatedIp(std::net::IpAddr);
// The only way to obtain a ValidatedIp is to pass validation.
impl TryFrom<RawInput> for ValidatedIp {
type Error = ParseError;
fn try_from(raw: RawInput) -> Result<Self, Self::Error> {
Ok(ValidatedIp(raw.0.trim().parse()?))
}
}
Now a function that takes a ValidatedIp cannot be handed unvalidated junk, it won't compile. The check isn't a code-review hope, it's a guarantee. Same idea is why Vyrox's response engine can't act on a malformed alert: the bad value never reaches a type the dangerous function will accept.
Fighting the borrow checker for an afternoon beats debugging a NoneType at 3am, every single time.