# Compile times & rust-analyzer performance

`#[Object]`, `#[derive(SimpleObject)]` and the other type-system macros expand to full
implementations of `ContainerType` and `OutputType` (schema registration, field dispatch,
resolvers, etc.) for every type they're applied to. On schemas with many large types this
generated code can make `cargo check`, `cargo build` and rust-analyzer's "check on save"
noticeably slower than a comparable hand-written schema, because rustc has to type-check every
line the macro produced.

## The `fast-check` feature

For faster `cargo check` and rust-analyzer feedback during development, gqlrs provides an
opt-in **`fast-check`** feature that generates minimal trait stubs instead of full
implementations:

```toml
[dependencies]
gqlrs = { version = "...", features = ["fast-check"] }
```

When `fast-check` is enabled:

- `#[Object]`, `#[derive(SimpleObject)]`, and `#[ComplexObject]` emit **stub implementations**
  of `ContainerType`, `OutputType`, and `ObjectType` that satisfy type-checking but skip the
  heavy per-field resolver dispatch, schema registration, and guard logic.
- `cargo check` completes much faster on large schemas because the compiler only needs to
  verify the type signatures, not the full resolver bodies.
- **IDE features still work**: types remain in scope, fields are visible for autocomplete, and
  go-to-definition navigates to the source struct/impl.

> ⚠️ **Important:** `fast-check` is intended for **development only**. The generated stubs
> return `Value::Null` for all fields and register no schema metadata. You **must disable it**
> before running `cargo build`, `cargo test`, or deploying to production. A good pattern is to
> hide it behind a local-only workspace feature that your CI and release builds never enable.

### Example: dev-only usage

```toml
# Cargo.toml
[features]
# Enable fast-check only during development
dev-fast-check = ["gqlrs/fast-check"]

[dependencies]
gqlrs = { version = "...", default-features = false }
```

Then during development:

```bash
cargo check --features dev-fast-check
```

To make rust-analyzer use it automatically during local development, configure
rust-analyzer to pass the Cargo feature (for example in your editor settings):

```json
{
  "rust-analyzer.cargo.features": ["dev-fast-check"]
}
```

## Why the macros can't be skipped automatically

It has been suggested that the heavy trait implementations generated by `#[Object]` and
`#[derive(SimpleObject)]` "aren't really necessary" when running `cargo check`, and could be
replaced with a cheap stub automatically. In practice this isn't safe to do: a proc-macro has
no reliable, stable way to tell whether it is being invoked for `cargo check`, `cargo build`,
`cargo test`, or rust-analyzer's own macro expansion — they all invoke the macro the same way,
because the expanded code is also what drives type inference and IDE features (go to
definition, autocomplete on resolver fields, etc.). Emitting a different, cheaper expansion
automatically would either:

- make `cargo check` pass on code that fails to `cargo build`, or
- silently disable IDE features (autocomplete, inline errors) that depend on the real expansion.

Both are worse than a slower `cargo check`, so gqlrs requires you to **explicitly opt in** via
the `fast-check` feature.

## Other mitigations

- **Split large schemas across crates/modules.** `cargo check` and rust-analyzer re-check a
  crate whenever any file in it changes. A single crate containing every `#[Object]`/
  `#[derive(SimpleObject)]` in your schema means *any* edit re-expands and re-checks all of it.
  Use [`MergedObject`/`MergedSubscription`](merging_objects.md) to split resolvers into several
  smaller crates (e.g. one per bounded context) so editing one only re-checks that crate and its
  dependents, not the whole schema.
- **Keep resolver-heavy crates separate from business logic.** If your domain/service crates
  don't themselves derive GraphQL types, changes to them never trigger re-expansion of the
  `#[Object]`/`#[derive(SimpleObject)]` macros at all.
- **Prefer several small `#[Object]` impls over one huge one.** Field dispatch and per-field
  registration code is generated per `impl` block, so one enormous object scales worse than the
  same fields split across a few smaller objects merged with `MergedObject`.
- **Only enable the crate features you use.** Each feature in gqlrs' `[features]` table (see the
  `Crate features` section of the README) pulls in more optional code paths. Turn off
  `default-features` and opt back in to only what you need to reduce the amount of code the
  compiler has to check on every run.
- **Check a single crate instead of the whole workspace.** `cargo check -p your-crate` (and the
  equivalent rust-analyzer setting to scope checks to the current crate) avoids re-checking
  unrelated workspace members.
- **Use an incremental-friendly dev setup.** A fast linker (`lld`/`mold`), `sccache`, and keeping
  `rust-analyzer.check.command` set to `check` rather than `clippy` all shorten the
  edit/check loop; none of them are gqlrs-specific, but they compound well with the crate-
  splitting advice above.

None of these change the emitted GraphQL schema or runtime behavior — they only change how the
generated code is distributed across compilation units so incremental tooling has less to redo
on each edit.
