# Garde derives validation from a struct's own field attributes

> Garde is a Rust validation library that generates a Validate implementation from attribute macros on your fields, with rules for lengths, ranges, patterns and cross-field checks. It suits teams that want validation declared next to the type it guards, and it is not a framework that owns your request handling.

**jprochazk/garde** — A powerful validation library for Rust

- Repository: https://github.com/jprochazk/garde
- Stars: 902 · Forks: 47
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jprochazk-garde

## Validation declared on the struct, not beside it

Garde is a Rust validation library, and its central idea is a derive macro. You attach Validate to a type and put rule attributes on the fields, and the macro generates an implementation of the Validate trait so you can call validate on a value.

The basic example in the README shows the whole shape in one screen. Installing the crate is a single cargo command with the full feature set enabled:

```bash
cargo add garde -F full
```

Then the type carries its own rules:

```rust
#[derive(Validate)]
struct User<'a> {
    #[garde(ascii, length(min=3, max=25))]
    username: &'a str,
    #[garde(length(min=15))]
    password: &'a str,
}
```

and the value is checked at the point of use:

```rust
if let Err(e) = user.validate() {
    println!("invalid user: {e}");
}
```

The design decision here is worth naming. Many validation approaches keep the rules somewhere else, a schema document, a config file, a separate validation layer written by hand. Garde keeps them on the fields, so the constraint and the type it constrains cannot drift apart, and a reviewer reading a struct sees the constraints without opening another file. The trade is that the rules are Rust code, so they are checked by the compiler and versioned with the type, but they are also not readable by a non-programmer.

Enums are supported in the same way, with rules on the fields inside the variants, which the README demonstrates with a struct variant carrying a range and a tuple variant carrying an ascii rule.

## A rule table where each row carries a feature flag

The available rules are laid out as a table, and the table is the document worth studying before you commit. Each row gives the attribute syntax, what it checks, and the feature flag that gates it.

The ungated rules cover the everyday cases: required checks that a value is set, ascii, alphanumeric, ip with either IPv4 or IPv6, plus ipv4 and ipv6 separately, length with a mode plus min, max or equal, range for numbers with the same shape, contains, prefix and suffix for string-like values, matches to compare one field against another, dive for nested validation, skip, and custom to call a function or closure of your own.

The flagged rules are the ones that pull in a dependency or a parser. Email is described as an email according to the HTML5 spec and sits behind the email feature. Url sits behind url. Credit card sits behind credit-card, phone number behind phone-number, and a regular expression pattern sits behind regex. A second pattern row takes a Matcher instead, and that variant needs no feature flag.

That last pair is a good example of the design. The regex variant means bringing in a regex engine, while the Matcher variant lets you plug in whatever matcher you already have. The choice of which one to depend on is left to the project, and the feature flags keep the default build small.

Reading the table as a whole, the rule set is broad enough to cover ordinary API input validation, and the feature flags mean you only pay for what you switch on.

## The macro crate is separate from the runtime crate

The repository is a Cargo workspace with two members, and the split is the mechanism behind the derive:

```toml
[workspace]
members = ["garde", "garde_derive"]
resolver = "2"

[workspace.package]
rust-version = "1.87"
version = "0.23.0"
```

One member is garde, the runtime library, and the other is garde_derive, the procedural macro. The workspace pins both to the same version, and the workspace dependency table refers to each by path with an exact version requirement and default-features turned off, so feature selection happens in the crate that pulls garde in rather than in the workspace.

The minimum Rust version is 1.87, which is a real constraint for anyone on an older toolchain, and it is stated in the workspace package section rather than buried in the crate manifest. The current workspace version is 0.23.0, matching the most recent published release, v0.23.0 on 2026-05-23.

The build profile section is a small sign of how the project treats its own test tooling. insta and trybuild are both compiled with opt-level 3 in the dev profile, which means the snapshot testing and compile-fail testing frameworks run at full optimisation rather than in a debug build. For a library whose main risk is what the macro emits, that is the right call: the compile-fail cases are where a derive macro usually breaks.

The repository also has an xtask directory, the common Rust convention for build and release automation kept out of the main cargo workflow, plus a deny.toml for dependency linting and a rust-toolchain.toml pinning the toolchain for contributors.

## Cross-field rules, nested values and custom functions

Three capabilities decide whether a validation library fits a codebase, and all three are present in garde.

Cross-field comparison is what the matches rule does, taking another field on the struct. Password confirmation is the obvious case, and a form where two fields must agree is exactly the situation where validating each field separately gives a misleading result. The rule set also includes conditional validation, listed in the README's table of contents, which is the mechanism for a rule that only applies when another field has a particular value.

Nested values are handled by the dive rule, which calls validate on the nested value. A request body containing a list of line items, each with its own rules, is the standard case, and dive is what lets the outer type delegate rather than needing its own traversal code.

The custom rule takes a function or a closure, which is the escape hatch for anything the built-in table does not cover. A function that checks whether an identifier is already taken, for instance, is not expressible as a length or a range, and the rule set has a slot for it.

The context and self access section of the README covers how a rule can reach the surrounding value rather than just the field it is attached to, and the custom rule with a function is the case where that matters. The rule adapters section is a related idea: an adapter wraps a rule with extra behaviour rather than being a rule in its own right.

The handling of Option and the newtypes section complete the picture. The newtypes pattern, where a validated type is distinct from its inner representation, is what stops a validated string from being passed around as a plain string, and the library supports it as a first-class case rather than a convention you have to police by hand.

## Where the fit is wrong, and what to check first

The first limitation is the one that comes with any derive-based approach: the rules are Rust, so they are only as accessible as the codebase. If your product includes people who need to read or change validation rules without reading Rust, garde will not serve them, and a schema-driven validator would be a better fit.

The second is the error type. When validate returns an error, that error is the library's own type, shaped the way garde shapes it. If your API contract already specifies an error format, from another service, from a client library, or from a published schema, then the mapping from the library's error to that format is work you own. The README does not describe a configurable error rendering layer, so anyone planning to expose these errors over an API should check that gap early rather than after wiring up the first endpoint.

The third is the feature flag surface. Each flagged rule is a separate switch, and the full set is turned on with -F full in the install command. The consequence is that a dependency tree which looks small in the documentation can grow considerably once email, url, credit card, phone number and regex are all enabled, and each of those brings a parser that has its own behaviour and its own release cycle. Turning on a rule for one field and paying for it across the whole project is the trade being made.

There is also the ordinary matter of a version below 1.0. The project is at 0.23.0, and a pre-1.0 crate in the validation path of a service is a deliberate risk that a team should take knowingly rather than by accident.

## Apache-2.0 terms and a release pace worth planning around

The licence field on the repository says Apache-2.0, and the repository root carries both LICENSE-APACHE and LICENSE-MIT files, so the terms are available to read in full. Apache-2.0 is a permissive licence with an express patent grant and a notice-preservation requirement, and it does not reach into the rest of a codebase that merely uses the crate. That is the straightforward case for a library sitting in the validation layer of a service.

The release history is worth understanding, because it explains what to expect when you upgrade. The recent versions are v0.23.0 on 2026-05-23, v0.22.1 on 2025-11-30 and v0.22.0 on 2025-01-19. That is a slow and deliberate pace, with a long gap between the two 0.22 point releases and a larger one before them. The last push to the repository was on 2026-07-31, so work is happening on the branch even when no release has been cut for it.

A slow cadence is a reasonable trade for a library that other people's types depend on, and it pairs well with the pre-1.0 version number, which signals that breaking changes are still considered acceptable. It does mean a team should read the changelog between versions rather than assume a patch release is a no-op, and it means the branch and the published crate can differ, which is the usual reason to consume the released version rather than a git dependency.

## Conclusion

Garde suits a Rust team that wants validation rules to live in the type definition rather than in a schema file or a hand-written validation layer, and that is comfortable with a procedural macro doing the work at compile time. It is the wrong choice for a project whose inputs are validated by a separate schema service, and for one that needs a specific error format to match an existing API contract, because the error type is the library's own. Before you adopt it, check that the rules you need exist in the table rather than assuming, since each rule is behind its own feature flag, and read the section on context and self access before you rely on a rule that needs to compare one field against another.

## FAQ

### How do I add garde to a Rust project?

The README gives the install as a single cargo command with the full feature set, cargo add garde -F full. You then attach the Validate derive to your type and call validate on a value, with rules declared as attributes on the fields.

### What validation rules does garde provide?

The rule table covers required, ascii, alphanumeric, ip, ipv4, ipv6, length, range, matches, contains, prefix, suffix, pattern, dive, skip and custom, plus email, url, credit card and phone number. Each of email, url, credit-card, phone-number and regex sits behind its own feature flag.

### How does garde validate a field against another field?

The matches rule takes the name of another field and checks the two against each other, which is how a password confirmation or a date range that spans two fields is expressed. Conditional validation covers rules that only apply when another field holds a particular value.

### What Rust version does garde require?

The Cargo workspace declares rust-version 1.87 in the workspace package section. The workspace holds two members, garde and garde_derive, and the current workspace version is 0.23.0, matching the most recent release.

## Sources

- [Issues](https://github.com/jprochazk/garde/issues)
- [jprochazk/garde on GitHub](https://github.com/jprochazk/garde)
- [License: Apache-2.0](https://github.com/jprochazk/garde/blob/main/LICENSE)
- [README](https://github.com/jprochazk/garde/blob/main/README.md)
- [Releases](https://github.com/jprochazk/garde/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jprochazk-garde
