# tree-sitter-rust: the Rust grammar behind fast incremental parsing

> tree-sitter-rust is the Rust grammar for tree-sitter, and its pitch is two to three times slower than rustc on the first parse but able to update an existing tree in under a millisecond when you edit a file. It ships bindings for Node, Python, Rust, Go and Swift, plus a WebAssembly build.

**tree-sitter/tree-sitter-rust** — Rust grammar for tree-sitter

- Repository: https://github.com/tree-sitter/tree-sitter-rust
- Stars: 531 · Forks: 162
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/tree-sitter-tree-sitter-rust

## What a tree-sitter grammar actually is

tree-sitter-rust is a Rust grammar for tree-sitter, and the repository itself is small and mostly declarative.

The tree-sitter project is an incremental parsing system. A grammar is a set of rules describing the syntax of a language, and the parser it generates produces a concrete syntax tree that can be updated in place when the source changes, rather than rebuilt from scratch. That property is what makes tree-sitter popular in editors: the tree stays valid across keystrokes, so queries can run against it continuously without waiting for a full reparse.

The repository layout shows the shape of one of these grammars. grammar.js is the source of truth, written in a JavaScript DSL for describing the syntax, and src/ holds the generated parser.c and scanner.c that the tree-sitter CLI produces from it. queries/ holds the query files, which are the pattern definitions used to find things like function definitions or macro invocations in a parsed tree. bindings/ holds the language-specific wrappers, and there is one per ecosystem: node, python, rust, go and swift, with binding.gyp for the Node native build, setup.py for the Python extension module, Cargo.toml and bindings/rust/lib.rs for the Rust crate, go.mod for the Go binding, and Package.swift with Package.resolved for Swift.

That five-binding spread is the practical face of the project. A grammar is only useful if you can load it from the language your tooling is written in, and tree-sitter-rust covers the mainstream options, plus a WebAssembly build for the browser.

The primary language is listed as JavaScript, which is worth noting because it is the grammar source rather than the parser. The MIT licence, the master default branch and the absence of GitHub releases complete the picture. The recent releases are v0.24.2 from 2026-03-27, v0.24.1 from 2026-03-19 and v0.24.0 from 2025-04-01, and the last push was on 2026-09-16, which is a reminder that the npm, crates.io and PyPI version numbers move independently of the repository's commit activity.

## The performance claim, in the terms the README uses

The one feature the README headlines is speed, and it states the claim with a measurement attached rather than a vague adjective.

When initially parsing a file, tree-sitter-rust takes around two to three times as long as rustc's hand-written parser.

The example in the README shows the two measurements side by side.

```sh
$ wc -l examples/ast.rs
  2157 examples/ast.rs

$ rustc -Z unpretty=ast-tree -Z time-passes examples/ast.rs | head -n0
  time:   0.002; rss:   55MB ->   60MB (   +5MB)  parse_crate

$ tree-sitter parse examples/ast.rs --quiet --time
  examples/ast.rs    6.48 ms        9908 bytes/ms
```

So a 2157 line file takes 6.48 milliseconds to parse with tree-sitter, against 2 milliseconds for rustc's own parser stage. That is the two to three times figure, and it is a cold parse of a whole file.

The second half of the claim is where it becomes interesting. If you edit the file after parsing it, tree-sitter can generally update the previous existing syntax tree to reflect your edit in less than a millisecond, thanks to its incremental parsing system.

That is the actual value proposition, and the two numbers should be read together rather than separately. A first parse that is three times slower than rustc is irrelevant on its own, because rustc's parser is not incremental and does not give you a tree you can query in a running editor. What matters is the cost of the second and subsequent parses, which is where the millisecond figure applies.

So the trade is a slower cold start in exchange for edits that cost almost nothing. For a language server or a CI linter that parses a large tree once, the cold number is what matters. For an editor that reparses on every keystroke, the incremental number is what matters, and there the ordering is reversed completely.

The grammar is also not a parser for Rust code in the compiler sense, and the README's References section is candid about the limits of the source material. It points to The Rust Reference, noting that while Rust does not have a specification, the reference tries to describe its working in detail, and that it tends to be out of date. It also points to the Keywords and Operators and Symbols appendices as the more stable source for the lexical layer.

That distinction is the important one for anyone evaluating a grammar. A tree-sitter grammar describes the syntax that real code exhibits, and where the language and the grammar disagree, the grammar follows the code.

## The bindings, and how each ecosystem consumes the grammar

Each binding in the tree does the same job in a different language's idiom, and reading them side by side is the quickest way to understand what the project ships.

The Node binding is the one the package.json treats as the main entry, with main and types both pointing at bindings/node. Its install script runs node-gyp-build, which means it prefers a prebuilt binary and falls back to compiling with node-gyp, and prebuilds/** and *.wasm are in the published file list, so prebuilt native binaries and a WebAssembly build are both shipped to npm. The peer dependency is tree-sitter at ^0.25.0, and it is marked optional in peerDependenciesMeta, so the grammar can be installed on its own. The test script runs the node test runner against bindings/node/*_test.js.

The Python binding is the most involved, and the reason is the limited API. setup.py defines a custom build command that copies the queries/ directory into the built package, because the query files are data the Python binding needs to ship alongside the extension. It also overrides the wheel tag so that any CPython version gets a cp39 abi3 wheel, which is what makes a single wheel work across Python versions rather than one wheel per version. The extension itself is built with a -std=c11 flag on non-Windows and /std:c11 with /utf-8 on Windows, defines Py_LIMITED_API against 0x03090000, and compiles binding.c together with src/parser.c and src/scanner.c. The pyproject.toml requires Python 3.9 or newer and adds an optional core extra pinning tree-sitter to the 0.22 series.

The Rust binding is the crate, and it is the one a Rust project would depend on directly. It uses edition 2021, a build script at bindings/rust/build.rs, a cc build dependency to compile the C parser, and tree-sitter-language at 0.1 as its only runtime dependency. The dev dependency is tree-sitter at 0.25.

The Go binding is covered by go.mod and go.sum, and the Swift one by Package.swift and Package.resolved, so a Swift package can consume the grammar directly.

Every binding follows the same pattern: compile the generated parser and scanner into the language's extension model, and expose the language object that the tree-sitter runtime loads. The grammar.js file and the generated src/ are shared, and the generated parser is what each of them embeds.

## Working on the grammar, and the tooling around it

Editing a grammar is a different task from writing a parser, and the repository's tooling is built for that.

The source is grammar.js, and the lint script runs eslint over grammar.js alone, with the eslint-config-treesitter configuration, so the grammar source is checked by a rule set written for grammars rather than for general JavaScript. The dev dependency on tree-sitter-cli at 0.26.7 is what compiles grammar.js into src/parser.c and src/scanner.c.

There is a playground and a WebAssembly path. The prestart script runs tree-sitter build --wasm and the start script runs tree-sitter playground, so you can edit a grammar and look at the resulting parse tree in a browser without writing any binding code. The wasm build also explains why *.wasm is in the published npm file list, which makes the grammar usable in a browser extension or a web-based code editor without a native module.

The queries/ directory is the other half of the grammar as far as most consumers are concerned. A grammar tells you the shape of the tree; queries are the patterns that pick out what you actually want from it. The Python build command copies that directory into the package, and the npm file list includes queries/*, which tells you the queries are treated as a first-class part of the distribution rather than as an afterthought.

The test directory holds the corpus tests, and the examples directory has two Rust files, examples/ast.rs and examples/weird-exprs.rs. The naming of the second one is a good hint about the priorities: a grammar for Rust has to cope with Rust's expression syntax, where the parsing is genuinely hard, so the test corpus deliberately includes awkward expressions rather than only straightforward code.

The test/ directory at the top level is the corpus of expected parse trees. For a grammar project, that corpus is the specification, and the interesting engineering is in keeping it aligned with the grammar rules as Rust's syntax evolves.

## Where tree-sitter-rust is the wrong tool

There are three limits, and the first is about what the grammar is authoritative for.

The first is that a tree-sitter grammar is not a Rust compiler front end, and the README is explicit that Rust does not have a specification while the reference tends to be out of date. That means the grammar is a best-effort description of the syntax that real Rust programs exhibit, maintained by people reading real code and the reference together. It will be more forgiving than rustc in places, and stricter in others, and it will not tell you about type errors, borrow errors or trait resolution at all. If you need to know whether a program is valid Rust, you need rustc, and the tree is the wrong input to that question.

The second is the incremental advantage's scope. The sub-millisecond update figure applies when you edit a file that has already been parsed and the tree is being reused. It does not apply to a cold parse of a large file, which is two to three times slower than rustc's parser, and it does not apply to a different file. A CI job that parses a thousand files once pays the cold cost every time, and a tool that reparses a whole tree rather than an edit pays it too.

The third is the versioning story across five ecosystems. The repository has no GitHub releases, and the version numbers on npm, crates.io and PyPI are maintained separately as part of the tree-sitter grammar publishing process. The package.json here is at 0.24.2, matching the Cargo.toml and pyproject.toml, but the Rust tree-sitter crate's dev dependency is tree-sitter 0.25 while the Python optional core extra pins tree-sitter to the 0.22 series, so the runtime the grammar is compiled against differs by ecosystem. In practice that means the grammar is shared and the runtime is not, and the failure mode of a mismatch is usually a load error rather than a wrong parse.

The honest alternative depends on what you are building. For syntax highlighting, code navigation and structural search in an editor, tree-sitter is the right tool and this grammar is the right one. For linting with semantic rules, a compiler-based tool such as Clippy is the right tool, because a lint that needs to know types needs types. For formatting or for compile-error reporting, rust-analyzer is the right tool, since it drives rustc's own analysis.

What tree-sitter-rust offers that rustc-based tooling does not is speed of access. You can point a query at a tree in microseconds and get an answer about structure without asking the compiler to type-check anything, which is why editors and lightweight analysis tools use it and why a grammar for Rust that stays current is worth having.

## Conclusion

Adopt tree-sitter-rust if you are building editor tooling, linting or code analysis on Rust and want incremental parsing rather than re-parsing whole files. Do not adopt it if you need a compiler-accurate Rust parser, because the README notes that Rust has no specification and that the reference tends to be out of date. Before you start, run the parser over the code you care about and check the queries, and note the last push was on 2026-09-16 with v0.24.2 from 2026-03-27.

## FAQ

### What is tree-sitter-rust and what does it do?

It is the Rust grammar for tree-sitter, the project that provides an incremental parsing system where an existing syntax tree can be updated to reflect an edit instead of being rebuilt. The grammar is written in grammar.js and compiled into a parser, and it ships bindings for Node, Python, Rust, Go and Swift plus a WebAssembly build.

### How fast is tree-sitter-rust compared to rustc?

The README states that when initially parsing a file, tree-sitter-rust takes around two to three times as long as rustc's hand-written parser, with the example showing 6.48 ms for a 2157 line file against 2 ms for rustc's parse_crate stage. However, if you edit the file after parsing it, tree-sitter can generally update the previous existing syntax tree to reflect your edit in less than a millisecond.

### Which language bindings does tree-sitter-rust provide?

The repository ships bindings for Node, Python, Rust, Go and Swift, with binding.gyp for Node, setup.py for Python, Cargo.toml and bindings/rust/lib.rs for Rust, go.mod for Go and Package.swift for Swift. A WebAssembly build is produced by tree-sitter build --wasm and shipped in the npm package.

### How do I work on the tree-sitter-rust grammar?

The grammar source is grammar.js and is linted with eslint using eslint-config-treesitter, while tree-sitter-cli compiles it into src/parser.c and src/scanner.c. The npm scripts prestart and start build the grammar for WebAssembly and open the tree-sitter playground, and the test/ directory holds the corpus of expected parse trees.

### Is tree-sitter-rust suitable for checking whether Rust code compiles?

No. It is a syntax grammar, not a compiler front end, and the README notes that Rust does not have a specification and that the reference tends to be out of date, so the grammar describes the syntax real programs exhibit. It reports nothing about type errors, borrow errors or trait resolution, so compiler-based tooling is the right choice for those questions.

## Sources

- [Issues](https://github.com/tree-sitter/tree-sitter-rust/issues)
- [License: MIT](https://github.com/tree-sitter/tree-sitter-rust/blob/master/LICENSE)
- [README](https://github.com/tree-sitter/tree-sitter-rust/blob/master/README.md)
- [Releases](https://github.com/tree-sitter/tree-sitter-rust/releases)
- [tree-sitter/tree-sitter-rust on GitHub](https://github.com/tree-sitter/tree-sitter-rust)

---

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