A practical guide for using qi to search and analyze Rust codebases. It focuses on Rust-specific patterns: visibility, modifiers, traits and impl blocks, pattern matching, async/unsafe, macros, and FFI.
Every query in this guide works against the example sources in
scratch/sources/rust/ — index them with ./index-rust scratch/sources/rust --once
to follow along.
- Quick Start
- Core Concepts
- Finding Definitions
- Visibility & Modifiers
- Traits & Impl Blocks
- Enums & Pattern Matching
- Structs & Construction Sites
- Async & Unsafe
- Macros & FFI
- Modules & Imports
- Common Workflows
- Advanced Queries
- Troubleshooting & Pitfalls
- Quick Reference
# Index current directory (runs as a daemon, watching for changes)
index-rust .
# Index once and exit
index-rust ./src --once
# Index multiple crates in a workspace
index-rust ./crates/core ./crates/cli ./crates/serverThe indexer skips target/ and other build directories listed in
rust/config/ignore_files.txt.
# 1. Map the entry points' structure
qi % -f src/lib.rs --toc
qi % -f src/main.rs --toc
# 2. The public API surface
qi % -s pub --def -i func class enum trait --limit 40
# 3. The trait landscape (the type system's backbone)
qi % -i trait --def
# 4. Expand the key types you saw
qi Config AppError -i class enum -e
# 5. Walk the main entry point
qi % --within main -x noise --limit 30This reveals: what the crate exposes → what traits govern behavior → what the core types look like → what the program does at startup.
qi % -f file.rs --toc # 1. structure first
qi handle_request -i func -e # 2. read a definition inline
qi handle_request -i call -C 3 # 3. find and inspect callers
qi token --within handle_request # 4. explore inside a function--toc gives you the map, -e shows the code without opening the file,
--within respects function boundaries (no false matches from neighbors).
| Context | Short | Rust constructs |
|---|---|---|
function |
func |
fn definitions: free functions, methods, trait signatures, FFI declarations |
argument |
arg |
Function parameters; identifier arguments at call sites (clue = callee) |
variable |
var |
let / const / static bindings; pattern bindings (match arms, if-let, for) |
property |
prop |
Struct and enum-variant fields; field accesses (self.name) and initializers |
class |
class |
struct, union, impl blocks; struct-literal and pattern-constructor usages |
enum |
enum |
enum definitions |
case |
case |
Enum variants; scoped variant references in patterns (Shape::Circle =>) |
trait |
trait |
trait definitions; trait bounds, supertraits, where clauses, derive contents |
type |
type |
type aliases and associated types (incl. GATs) |
namespace |
ns |
mod declarations |
import |
imp |
use items (incl. pub use re-exports) and extern crate |
call |
call |
Function, method, and macro calls |
lambda |
lambda |
Closures (indexed as <closure> alongside their binding) |
macro |
macro |
macro_rules! definitions |
comment |
com |
Words from comments |
string |
str |
Words from string literals |
filename |
file |
Filename without extension |
Explicit pub visibility is captured exactly as written; items with no
visibility keyword default to private. This applies uniformly to every item
kind, including pub mod declarations.
| Scope | Meaning |
|---|---|
pub |
Fully public |
pub(crate) |
Crate-private |
pub(super) |
Visible to parent module |
private |
Module-private (Rust default; no pub written) |
| Modifier | Applies to | Meaning |
|---|---|---|
async |
functions | Returns a Future |
unsafe |
functions | Requires unsafe to call |
const |
functions | Callable at compile time |
extern "C", extern "system", ... |
functions, statics | FFI ABI — set on extern fn and on every declaration inside an extern { } block |
await |
calls | The call was .await-ed |
mut |
variables, arguments | Mutable binding: let mut x, fn f(mut x: T), static mut X, Some(mut x) patterns |
Modifiers combine with everything else: pub const fn → scope pub, modifier const;
static mut inside extern "C" { } → modifier extern "C" mut.
| Clue | Where | Meaning |
|---|---|---|
#[test], #[inline], #[derive], ... |
definitions | Outer attributes, comma-joined when stacked: #[should_panic],#[test]. Inner attributes (#![...]) are not attributed to the following item. |
impl Display |
impl blocks | The impl implements the named trait |
impl |
impl blocks | Inherent impl (no trait) |
derive |
trait usages | The trait reference came from a #[derive(...)] list |
as |
imports | Aliased import: use foo as bar, extern crate proc_macro as pm |
| (callee name) | call arguments | format, println, etc. — which call this argument belongs to |
The PAR column holds the nearest enclosing or qualifying symbol:
- Methods → the impl target (
impl User→ parentUser) or trait (for trait methods) - Enum variants → the enum; variant fields → the variant (
width→Rect) - Struct fields → the struct
- Associated consts and types → the trait/impl
- Method calls → the receiver:
stack.pop()→stack,self.read(...)→self. In a chain, each link's parent is the previous link:text.trim().parse()→trim|text,parse|trim - Field accesses → the immediate owner:
person.address.city→city|address - Struct-literal fields → the let-bound variable when assigned
(
let addr = Address { street: ... }→street|addr), the enclosing field for nested literals, otherwise the literal's type name (Person { name }in return position →name|Person)
Module-path qualifiers, captured for:
- imports:
use std::io::{Read, Write}→Read|std::io(brace groups and nested groups resolve fully:use a::{b::{C}}→C|a::b) - scoped calls:
AppError::Io(e)→Io|AppError,std::fs::read_to_string(p)→ NSstd::fs - scoped patterns:
Shape::Circle(r) =>→Circle|Shape - scoped trait bounds:
K: std::hash::Hash→Hash|std::hash - aliases:
use auth::tokens::generate as gen_token→gen_tokenwith the full source path
The TYPE column holds the annotation text verbatim:
- function return types:
Result<String, std::io::Error> let x: T, parameters, fields, consts:T- type aliases: the aliased type (
type Cache<T> = HashMap<UserId, T>→HashMap<UserId, T>) macro_rules!for macro definitions;macrofor macro invocations
qi parse_config -i func # find it
qi parse_config -i func -e # full body inline
qi % -i func -f src/parser.rs # all functions in a file
qi % -i func -p User # methods on a type (impl target)
qi % -i func -p Storage # trait method signatures
qi new with_% from_% -i func --def # constructors by convention
qi % -i func -p User -t Self # methods returning Self (builders)qi User -i class -e # struct definition, expanded
qi % -i class --def -f src/models/ # all structs in a module
qi % -i prop -p Config # fields of a struct
qi Status -i enum -e # enum definition
qi % -i case -p Status --def # its variants
qi % -i prop -p Rect # fields of a struct-like variant
qi Storage -i trait -e # trait definitionqi MAX_RETRIES -i var # a const/static/let binding
qi % -i var -f src/constants.rs --def # all consts in a file
qi % -i var -t 'Vec<%' --def # bindings with a Vec type annotation
qi % -i var -m mut --def # mutable bindings
qi % -i var -p Storage # associated consts of a trait
qi AppResult -i type # type alias (TYPE column shows the target)
qi % -i type -p Container # associated types: type Item; / GATsqi % -s pub --def --limit 50 # everything public
qi % -i func -s pub --def # public functions
qi % -i class enum trait -s pub --def # public types
qi % -s 'pub(crate)' --def # crate-private items
qi %helper% %internal% -s pub --def # inadvertently-public helpers?qi % -m mut --def # every mutable binding
qi % -i var -m '%mut%' -f src/ffi/ # mutable statics (incl. extern blocks)
qi % -i arg -m mut # mut-by-value parametersqi % -i func -m const --def
qi % -i func -s pub -m const --defqi Serializable -i trait -e # define-site
qi % -i trait --def # all traits in the crate
qi % -i func -p Storage # method signatures (bodyless ones included)
qi % -i var -p Container # associated consts (const CAPACITY: usize)
qi % -i type -p Stream # associated types (type Item<'a>)Impl blocks are class entries whose clue records the trait: impl Display
(or just impl for inherent impls). The symbol is the impl target.
qi % -i class -c impl --def # inherent impls
qi % -i class -c 'impl %' --def # all trait impls
qi % -i class -c 'impl Display' # who implements Display?
qi % -i class -c 'impl From' # all From conversions
qi User -i class --def # struct def + every impl block for UserEvery trait named in a bound is indexed as a trait usage — inline bounds
(<T: Clone>), where clauses, supertraits, and associated-type bounds alike.
Scoped paths carry their namespace.
qi Display -i trait --usage # everywhere Display is required as a bound
qi % -i trait --usage -f src/ # the crate's full constraint vocabulary
qi Hash -i trait -ns std::hash # scoped bound: where K: std::hash::Hash
qi Send Sync -i trait --usage # supertraits: trait Serializable: Send + SyncNames inside #[derive(...)] are indexed as trait usages with clue derive:
qi % -i trait -c derive # every derived trait, everywhere
qi Serialize -i trait -c derive # which types derive Serialize?
qi Clone -i trait --usage # Clone as a bound *and* as a deriveqi serialize -i call # all calls to a method
qi % -i call -p self # all self.method() calls
qi lock -i call -p mutex # specific method on specific receiver
qi parse -i call # includes turbofish calls: .parse::<i32>()Chained calls keep their link-to-link parents, so -p works mid-chain:
# nums.iter().map(...).collect() indexes: iter|nums, map|iter, collect|map
qi collect -i call -p mapqi % -i case -p Status --def # variants of an enum
qi % -i case --usage # variant references in match/if-let patterns
qi % -i case -ns Shape # arms matching on Shape::*
qi NotFound -i case -ns AppError # one variant, pattern position
qi Io -i call -ns AppError # same variant, construction positionScoped paths in patterns (AppError::NotFound =>) are indexed as case usages
with the path in NS. Bare constructors in patterns (Ok(v), Point { .. }) are
indexed as class usages.
Bindings are extracted from match arms, if let, while let, for, and let
destructuring — including struct-pattern shorthand:
# Shape::Rect { width, height } => ... binds width and height as VARs
qi width height -i var --def
# renamed bindings: let Point { x: px, y: py } = ...
qi px py -i var
# mut bindings in patterns: Some(mut conn) => ...
qi % -i var -m mut --defUppercase-start identifiers in pattern position (None, Pending) are treated
as constructor/constant references, not bindings, so they never pollute variable
searches. One-character bindings (x, i) are dropped by the noise filter.
Struct literals index both the type name (a class usage) and each field:
qi User -i class --usage # every place a User { ... } is built
qi Person --usage -C 2 # construction sites with context
# Fields at initialization sites:
qi street -i prop -p addr # let addr = Address { street: ... }
qi name -i prop -p Person # Person { name, .. } in return position
qi age -i prop --usage # all initializations + accesses of `age`Field-access chains keep per-link parents:
# person.address.city indexes: address|person, city|address
qi city -i prop -p addressqi % -i func -m async --def # all async functions
qi % -i func -s pub -m async --def # public async API
qi % -i call -m await # every await point
qi % -i call -m await --within fetch_data # awaits inside one function
qi % -i call -m await -p client # awaited calls on one receiverqi % -i func -m unsafe --def # the audit surface
qi % -i func -m unsafe -s pub --def # unsafe public API (highest priority)
qi % -i call --within do_unsafe # what unsafe functions call
qi transmute -i call -C 3 # always worth a lookmacro_rules! definitions have their own context:
qi % -i macro # all macro definitions
qi debug_print -i macro -e # expand oneNote: macro bodies are token trees and are not indexed — calls inside a
macro_rules! definition won't appear in the index.
Macro calls are call entries with macro in the TYPE column:
qi % -i call -t macro # all macro invocations
qi println panic assert_eq -i call # specific macros
qi % -i call -t macro --within main # macros used in one function
# Arguments passed to macros carry the macro name as their clue:
qi % -i arg -c format # everything passed to format!# Declarations inside extern blocks get the block's ABI as modifier
qi % -i func -m 'extern "C"' --def # foreign fns + #[no_mangle] exports
qi % -i func -m 'extern "system"' --def # other ABIs
qi % -i var -m 'extern%' --def # foreign statics (errno etc.)
qi % -i func -c '%no_mangle%' --def # exported with C ABI
# extern crate declarations are imports
qi alloc -i imp # extern crate alloc;
qi pm -i imp -c as # extern crate proc_macro as pm;qi % -i ns --def # all module declarations
qi % -i ns -c '#[cfg]' # conditionally-compiled modules (mod tests)
qi tests -i ns --def # test modules by conventionThe NS column holds the source path, including brace groups and nested groups:
qi % -i imp --limit 30 # all imported names
qi % -i imp -ns std::collections # from a specific path
qi % -i imp -ns 'serde%' # external dependency usage
qi % -i imp -ns 'crate%' # internal imports
qi % -i imp -c as # aliases: use foo as bar
qi % -i imp -f src/lib.rs # what lib.rs pulls in (incl. pub use)qi new -i call -ns Vec # Vec::new()
qi spawn -i call -ns 'tokio%' # tokio::spawn, tokio::task::spawn...
qi % -i call -ns 'std::fs' # all std::fs calls (sync IO audit)qi handle_request -i func # 1. find it
qi handle_request -i func -e # 2. read it
qi % -i call --within handle_request --limit 30 # 3. what it calls
qi % -i call -m await --within handle_request # 4. where it awaits
qi handle_request -i call -C 3 # 5. who calls it, in contextqi User -i class -e # 1. definition
qi User -i class --def # 2. struct + every impl block
qi % -i func -p User # 3. methods
qi % -i prop -p User --def # 4. fields
qi User -i class --usage # 5. construction sites
qi % -i arg -t '%User%' # 6. parameters of this type
qi % -i func -t '%User%' --def # 7. functions returning itqi AppError -i enum -e # 1. the error enum
qi % -i case -p AppError --def # 2. variants
qi AppError -i class -c 'impl From' # 3. conversions feeding into it
qi AppError -i class -c 'impl Display' # 4. formatting impl
qi % -i func -t '%AppError%' --def # 5. functions producing it
qi % -i case -ns AppError # 6. where each variant is matched
qi % -i call -ns AppError # 7. where each variant is builtqi Storage -i trait -e # 1. the trait
qi % -i class -c 'impl Storage' --def # 2. all implementations
qi % -i func -p Storage --def # 3. the method set
qi read write delete -i call -C 2 # 4. call sites of those methods
qi Storage -i trait --usage # 5. where it's used as a bound
qi % -i arg -t '%dyn Storage%' -t '%impl Storage%' # 6. trait-object/impl paramsqi % -i func -c '#[test]' --def # all tests
qi % -i func -c '%should_panic%' --def # panic tests
qi % -i ns -c '#[cfg]' --def # #[cfg(test)] modules
qi parse_config -i call -f tests/ # tests exercising a functionAll filters compose; each one narrows the result set:
qi % -i func -p Client -s pub -m async --def # public async methods on Client
qi % -i func -s pub -c '#[deprecated]' --def # deprecated public API
qi % -i call -m await --limit-per-file 2 # sample await points per file
qi % -i trait -c derive -f src/models/ # derives in one moduleqi headers --within parse_request -x noise # one symbol inside one function
qi % -i var --within process_data --def # locals of a function
qi token --within authenticate authorize # multiple functions (OR)The TYPE column is matched as text, so wildcards work on the annotation:
qi % -i func -t 'Result<%' --def # functions returning Result
qi % -i arg -t '&str' # &str parameters
qi % -i arg -t '&mut %' # mutable-reference parameters
qi % -i arg -t 'impl %' # impl Trait parameters
qi % -i func -t 'impl Future%' # functions returning futuresqi fprintf stderr --and # both on the same line
qi lock unlock --and 5 # within 5 lines of each other
qi unwrap expect --and 10 # clustered panicking callsqi % -i func -v # all columns
qi % -i func --columns line sym par mod type # chosen columns (space-separated)
qi getUserById -i func -e --raw # bare source (good Edit anchors)
qi handle_request -C 5 # context lines
qi % -i call --limit-per-file 3 --limit 30 # distributed samplingqi my_function -i func # no results?
qi '%function%' # try partial match, no context filter
qi my_function -v # check what context it actually hasThe indexed symbol is the base name; the full generic expression lives in the TYPE column.
qi 'Vec<u8>' -i class # ✗ won't match — symbol is just "Vec"
qi Wrapper -i class --def # ✓ finds Wrapper<'a, T>
qi % -i var -t 'Vec<u8>' # ✓ full expressions go in -tRust keywords (self, Self, crate, ...) are never indexed, and symbols
shorter than two characters are dropped (x, i, type params T, K).
Current limitation: the shared stopword list is applied case-insensitively
to code symbols, so a few common English words are unfindable even when they are
real Rust symbols — notably from (String::from, impl From's fn from),
Some, and a Move variant. Prefer searching the surrounding context
(qi String -ns %, qi % -i case -ns Option) until this is fixed.
# ✗ grep for impl blocks # ✓ ask the index
grep -rn "impl Display" . qi % -i class -c 'impl Display'
# ✗ forget noise exclusion # ✓ skip comments/strings
qi error qi error -x noise
# ✗ comma-separated columns # ✓ space-separated
qi % --columns line,sym qi % --columns line sym
# ✗ macro filter that doesn't exist # ✓ macro calls live in the TYPE column
qi % -i call -c 'macro!' qi % -i call -t macro| Task | Command |
|---|---|
| File structure | qi % -f file.rs --toc |
| Expand a definition | qi name -i func -e |
| Public API | qi % -s pub --def |
| Async functions | qi % -i func -m async --def |
| Unsafe functions | qi % -i func -m unsafe --def |
| Mutable bindings | qi % -m mut --def |
| Methods on a type | qi % -i func -p Type |
| Fields of a type | qi % -i prop -p Type |
| Enum variants | qi % -i case -p Enum --def |
| Variant match sites | qi % -i case -ns Enum |
| Construction sites | qi Type -i class --usage |
| Trait impls | qi % -i class -c 'impl Trait' |
| Trait bounds | qi Trait -i trait --usage |
| Derived traits | qi % -i trait -c derive |
| Associated types | qi % -i type -p Trait |
| Associated consts | qi % -i var -p Trait |
| Await points | qi % -i call -m await |
| Macro definitions | qi % -i macro |
| Macro invocations | qi % -i call -t macro |
| FFI declarations | qi % -m 'extern%' --def |
| Imports from a path | qi % -i imp -ns std::io |
| Scoped calls | qi new -i call -ns Vec |
| Inside a function | qi token --within authenticate |
| Same-line patterns | qi lock unlock --and 5 |
| By type annotation | qi % -i arg -t 'Result<%' |
| Tests | qi % -i func -c '#[test]' --def |
- Handler source:
rust/rust_language.c - Configuration:
rust/config/{file_extensions,ignore_files,keywords}.txt - AST debugging:
tools/ast-explorer-rust file.rs— view tree-sitter output - Known-good examples:
scratch/sources/rust/*.rs— one file per language feature