| name | rust-analyzer-api |
| description | Guide to rust-analyzer's public API crates (ra_ap_*). Use this skill when building Rust analysis tools, code intelligence, linters, refactoring tools, or any programmatic consumer of rust-analyzer's libraries. Covers the full crate hierarchy: ide (Analysis/AnalysisHost), hir (Semantics, semantic model), syntax (CST/AST/parsing), ide_db (RootDatabase/Salsa), vfs (virtual filesystem), load-cargo (bootstrapping), project-model (Cargo discovery), and secondary crates (completion, SSR, hir_def, hir_ty, cfg, parser, paths, proc_macro_api). All crates at version 0.0.330. Do NOT use this skill for contributing to rust-analyzer itself or for rustc_driver-based tools (see the rustc-driver-frontend skill instead). |
rust-analyzer Public API Guide (ra_ap_* crates, v0.0.330)
When this applies
You're building a tool that analyzes Rust code at a semantic level — types, traits, name resolution, completions, diagnostics — and you want to reuse rust-analyzer's analysis engine as a library rather than talking to it over LSP. This is the right choice when:
- You need IDE-grade analysis (go-to-definition, find-references, type inference) in a batch tool
- You want to build a custom linter, refactoring tool, or code generator on top of rust-analyzer
- You need to parse Rust source into a full-fidelity syntax tree with error recovery
- You want SCIP/LSIF index generation, structural search-replace, or custom diagnostics
This is NOT the right choice when:
- You need rustc's exact type system, MIR, or monomorphization — use
rustc_driver (see rustc-driver-frontend skill)
- You just need to talk to rust-analyzer over LSP — use
lsp-types directly
- You need proc-macro expansion in isolation — use
proc-macro2/syn/quote
Architecture overview
┌─────────────────────────────────────────────────────────┐
│ ra_ap_rust-analyzer (LSP server binary — not the API) │
└────────────────────────┬────────────────────────────────┘
│ uses
┌────────────────────────▼────────────────────────────────┐
│ ra_ap_ide — Top-level IDE API │
│ Analysis / AnalysisHost │
├─────────────────────────────────────────────────────────┤
│ ra_ap_hir — High-level semantic model │
│ Semantics, Module, Function, Struct, Type, Trait │
├─────────────────────────────────────────────────────────┤
│ ra_ap_ide_db — Salsa database + symbol index │
│ RootDatabase, Definition, Search, LineIndex │
├──────────────────────┬──────────────────────────────────┤
│ ra_ap_hir_def │ ra_ap_hir_ty │
│ (definitions, nameres) (type inference, trait solving) │
├──────────────────────┴──────────────────────────────────┤
│ ra_ap_syntax — CST/AST, parsing │
│ SyntaxNode, SourceFile, ast::*, AstNode │
├─────────────────────────────────────────────────────────┤
│ ra_ap_parser — Token → syntax tree │
├─────────────────────────────────────────────────────────┤
│ Bootstrapping layer: │
│ ra_ap_load_cargo — Cargo project → RootDatabase │
│ ra_ap_project_model — Project/workspace discovery │
│ ra_ap_vfs — Virtual file system │
│ ra_ap_vfs_notify — File-watching VFS loader │
├─────────────────────────────────────────────────────────┤
│ Utilities: │
│ ra_ap_cfg, ra_ap_paths, ra_ap_proc_macro_api, │
│ ra_ap_ide_completion, ra_ap_ide_ssr │
└─────────────────────────────────────────────────────────┘
Bootstrapping: loading a Cargo project
The canonical way to get a working Analysis from a Cargo project on disk:
use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace_at};
use project_model::CargoConfig;
use ide::AnalysisHost;
let cargo_config = CargoConfig::default();
let load_config = LoadCargoConfig {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: true,
num_worker_threads: 4,
proc_macro_processes: 1,
};
let (db, vfs, _proc_macro_client) = load_workspace_at(
std::path::Path::new("/path/to/project"),
&cargo_config,
&load_config,
&|msg| eprintln!("{msg}"),
)?;
let analysis = db.analysis();
For more control, load in stages:
use project_model::{ProjectManifest, ProjectWorkspace};
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
let manifest = ProjectManifest::discover_single(&abs_path)?;
let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, &|_| {})?;
if want_build_scripts {
let build_scripts = workspace.run_build_scripts(&cargo_config, &|_| {})?;
workspace.set_build_scripts(build_scripts);
}
let (db, vfs, proc_macro_client) = load_workspace(
workspace,
&cargo_config.extra_env,
&load_config,
)?;
Key types in the bootstrap chain:
ProjectManifest — discovered Cargo.toml or rust-project.json
ProjectWorkspace — loaded project with three variants: Cargo, Json, DetachedFile
CargoConfig — controls cargo metadata invocation, target, features, env
LoadCargoConfig — controls proc-macro loading, cache priming, worker threads
RootDatabase — the Salsa incremental database with all analysis data
Vfs — virtual filesystem mapping FileId ↔ VfsPath
ra_ap_ide — Top-level IDE API
The main entry point for consumers. Provides AnalysisHost (mutable) and Analysis (immutable snapshot) with all IDE features.
AnalysisHost / Analysis
use ide::{AnalysisHost, Analysis, FilePosition, FileRange, FileId};
use hir::ChangeWithProcMacros;
let mut host = AnalysisHost::new( None);
let mut change = ChangeWithProcMacros::default();
host.apply_change(change);
let analysis: Analysis = host.analysis();
Navigation
analysis.goto_definition(FilePosition { file_id, offset })?;
analysis.goto_implementation(FilePosition { file_id, offset })?;
analysis.goto_type_definition(FilePosition { file_id, offset })?;
analysis.find_all_refs(FilePosition { file_id, offset }, search_scope)?;
Hover & signature help
let hover = analysis.hover(&hover_config, FileRange { file_id, range })?;
let sig = analysis.signature_help(FilePosition { file_id, offset })?;
Completions
let items: Vec<CompletionItem> = analysis.completions(
&completion_config,
FilePosition { file_id, offset },
trigger_char,
)?;
Diagnostics
analysis.syntax_diagnostics(&diagnostics_config, file_id)?;
analysis.semantic_diagnostics(&diagnostics_config, file_id)?;
analysis.full_diagnostics(&diagnostics_config, resolve, file_id)?;
Code actions / assists
let assists: Vec<Assist> = analysis.assists_with_fixes(
&assist_config,
&diagnostics_config,
resolve,
FileRange { file_id, range },
)?;
Other features
analysis.highlight(highlight_config, file_id)?;
analysis.inlay_hints(&inlay_hints_config, file_id, range)?;
analysis.file_structure(file_id)?;
analysis.symbol_search(query)?;
analysis.runnables(file_id)?;
analysis.rename(FilePosition { file_id, offset }, "new_name")?;
analysis.structural_search_replace(query, parse_only, resolve_context, selections)?;
analysis.incoming_calls(FilePosition { file_id, offset })?;
analysis.outgoing_calls(FilePosition { file_id, offset })?;
analysis.folding_ranges(file_id)?;
Key types
| Type | Purpose |
|---|
FileId | Opaque file identifier (u32 newtype) |
FilePosition | { file_id: FileId, offset: TextSize } |
FileRange | { file_id: FileId, range: TextRange } |
NavigationTarget | Location + metadata for go-to results |
SymbolKind | Function, Struct, Trait, Enum, Module, etc. |
CompletionItem | Completion with label, edit, kind, relevance |
Diagnostic | Error/warning with range, message, fixes |
Assist | Code action with label and SourceChange |
SourceChange | Set of file edits to apply atomically |
HlRange | Highlighted range with semantic tag |
Runnable | Test/bench/binary with cargo invocation info |
RangeInfo<T> | Result T with the source range it applies to |
ra_ap_hir — High-level semantic model
Object-oriented API over Rust's semantic structure. This is the crate you use when you need to navigate the module tree, inspect types, resolve traits, or map between syntax and semantics.
Semantics — the bridge between syntax and semantics
use hir::Semantics;
use ide_db::RootDatabase;
let sema = Semantics::new(&db);
let func: hir::Function = sema.to_def(&fn_syntax_node)?;
let module: hir::Module = sema.to_def(&module_syntax_node)?;
let ty: hir::Type = sema.type_of_expr(&expr)?.original;
let resolution = sema.resolve_path(&path_expr)?;
let expanded_tokens = sema.descend_into_macros(token);
let node: ast::Expr = sema.find_node_at_offset_with_descend(syntax, offset)?;
Module tree traversal
let krate: hir::Crate = ;
let root: hir::Module = krate.root_module(&db);
for child in root.children(&db) {
let name = child.name(&db);
}
let path: Vec<hir::Module> = module.path_to_root(&db);
let decls: Vec<hir::ModuleDef> = module.declarations(&db);
let scope = module.scope(&db, None);
Type inspection
let ty: hir::Type = ;
ty.as_adt()
ty.as_callable(&db)
ty.as_reference()
ty.fields(&db)
ty.autoderef(&db)
ty.impls_trait(&db, trait_, &[])
ty.iterate_method_candidates(&db, scope, &visible_traits, None, |method| {
Some(method)
});
Key semantic types
| Type | Represents |
|---|
Crate | A crate in the dependency graph |
Module | A module (file or inline mod {}) |
Function | fn item |
Struct, Enum, Union | ADT definitions |
Trait | Trait definition |
Impl | impl block |
TypeAlias | type Foo = ... |
Const, Static | const/static items |
Field | Struct/enum variant field |
EnumVariant | Enum variant |
Local | Local variable binding |
Type<'db> | A resolved type with environment context |
GenericParam | Type/const/lifetime parameter |
AssocItem | Associated function/type/const in a trait/impl |
Finding implementations
let impls: Vec<hir::Impl> = hir::Impl::all_for_type(&db, ty);
let impls: Vec<hir::Impl> = hir::Impl::all_for_trait(&db, trait_);
for item in impl_.items(&db) {
match item {
AssocItem::Function(f) => { }
AssocItem::TypeAlias(t) => { }
AssocItem::Const(c) => { }
}
}
Source mapping (semantic → syntax)
use hir::HasSource;
let source = func.source(&db)?;
ra_ap_syntax — Concrete syntax tree and parsing
Full-fidelity, error-resilient Rust parser. Built on the rowan library. Preserves all source text including whitespace and comments.
Parsing
use syntax::{SourceFile, Edition, AstNode};
let parse = SourceFile::parse(source_text, Edition::Edition2024);
let tree: SourceFile = parse.tree();
let errors: Vec<SyntaxError> = parse.errors();
let expr_parse = ast::Expr::parse(expr_text, Edition::Edition2024);
Untyped tree (SyntaxNode / SyntaxToken)
use syntax::{SyntaxNode, SyntaxToken, SyntaxKind, SyntaxElement};
let root: SyntaxNode = parse.syntax_node();
root.kind()
root.children()
root.children_with_tokens()
root.descendants()
root.parent()
root.ancestors()
for event in root.preorder_with_tokens() {
match event {
WalkEvent::Enter(element) => { }
WalkEvent::Leave(element) => { }
}
}
Typed AST wrappers
Zero-cost wrappers over SyntaxNode. Cast with AstNode::cast():
use syntax::ast::{self, AstNode, HasName, HasAttrs, HasVisibility};
let fn_node: ast::Fn = ast::Fn::cast(syntax_node)?;
fn_node.name()
fn_node.param_list()
fn_node.ret_type()
fn_node.body()
fn_node.visibility()
fn_node.attrs()
fn_node.syntax()
Major AST enums:
ast::Item — top-level items (Fn, Struct, Impl, Use, Mod, ...)
ast::Expr — expressions (BinExpr, CallExpr, IfExpr, MatchExpr, ...)
ast::Type — type expressions (PathType, RefType, TupleType, ...)
ast::Pat — patterns (IdentPat, TuplePat, WildcardPat, ...)
ast::Stmt — statements (LetStmt, ExprStmt, Item)
Common traits on AST nodes:
HasName — .name() → Option<ast::Name>
HasAttrs — .attrs() → iterator of ast::Attr
HasVisibility — .visibility() → Option<ast::Visibility>
HasGenericParams — .generic_param_list(), .where_clause()
HasDocComments — .doc_comments()
Pattern matching with match_ast!
use syntax::match_ast;
match_ast! {
match node {
ast::Fn(it) => { }
ast::Struct(it) => { }
ast::Impl(it) => { }
_ => { }
}
}
Finding nodes at positions
use syntax::algo;
let expr: ast::Expr = algo::find_node_at_offset(root.syntax(), offset)?;
let ancestors = algo::ancestors_at_offset(root.syntax(), offset);
Pointers (stable references across reparses)
use syntax::{SyntaxNodePtr, AstPtr};
let ptr = SyntaxNodePtr::new(&node);
let recovered: SyntaxNode = ptr.to_node(&new_root);
let fn_ptr = AstPtr::new(&fn_node);
let recovered: ast::Fn = fn_ptr.to_node(&new_root);
Text types
TextSize — byte offset (u32 newtype)
TextRange — start..end range of TextSize
node.text_range() — range in source
token.text() — the actual source text of a token
ra_ap_ide_db — Database and symbol infrastructure
The Salsa incremental database that underpins everything. You rarely construct this directly (use load_cargo instead), but you interact with its types constantly.
RootDatabase
use ide_db::RootDatabase;
let mut db = RootDatabase::new( None);
db.enable_proc_attr_macros();
db.apply_change(change);
Implements these Salsa database traits:
SourceDatabase — file text, crate graph
DefDatabase — definitions, name resolution
ExpandDatabase — macro expansion
HirDatabase — type inference, trait solving
Definition — unified representation of any Rust definition
use ide_db::defs::Definition;
match definition {
Definition::Function(f) => { }
Definition::Adt(adt) => { }
Definition::Trait(t) => { }
Definition::Module(m) => { }
Definition::Const(c) => { }
}
Symbol search
use ide_db::symbol_index::Query;
let mut query = Query::new("MyStruct".to_owned());
query.limit(10);
query.exact();
Find references / usage search
use ide_db::search::{UsageSearchResult, FileReference, SearchScope};
LineIndex — efficient line/column mapping
use ide_db::line_index::LineIndex;
let line_index = LineIndex::new(source_text);
let line_col = line_index.line_col(offset);
let offset = line_index.offset(line_col);
ra_ap_vfs — Virtual file system
Manages the mapping between file paths and FileIds. Tracks file changes for incremental updates.
use vfs::{Vfs, VfsPath, FileId};
let mut vfs = Vfs::default();
vfs.set_file_contents(VfsPath::from(abs_path), Some(contents));
let (file_id, excluded) = vfs.file_id(&path)?;
let path: &VfsPath = vfs.file_path(file_id);
let changes: IndexMap<FileId, ChangedFile> = vfs.take_changes();
for (_, changed) in changes {
match changed.change {
Change::Create(bytes, _) => { }
Change::Modify(bytes, _) => { }
Change::Delete => { }
}
}
VfsPath supports both real filesystem paths and virtual paths (for in-memory files):
let real = VfsPath::from(AbsPathBuf::assert_utf8(path));
let virtual_ = VfsPath::new_virtual_path("/virtual/file.rs".into());
File loader interface
The vfs::loader module defines the Handle trait for file loading/watching:
vfs_notify::NotifyHandle — real filesystem loader using notify crate
Entry::Directories { include, exclude, extensions } — what to load
Message::Loaded { files } / Message::Changed { files } — change notifications
ra_ap_project_model — Project discovery and modeling
Discovers and models Cargo workspaces, rust-project.json files, and sysroots.
Project discovery
use project_model::{ProjectManifest, ProjectWorkspace, CargoConfig};
let manifest = ProjectManifest::discover_single(&abs_path)?;
let manifests: Vec<ProjectManifest> = ProjectManifest::discover(&abs_path)?;
Loading a workspace
let cargo_config = CargoConfig {
set_test: true,
..CargoConfig::default()
};
let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, &|msg| {})?;
Build scripts and proc macros
let build_scripts = workspace.run_build_scripts(&cargo_config, &|_| {})?;
workspace.set_build_scripts(build_scripts);
let (crate_graph, proc_macros) = workspace.to_crate_graph(&mut load_file, &extra_env);
CargoWorkspace — Cargo-specific model
use project_model::CargoWorkspace;
for pkg in cargo_workspace.packages() {
let name = cargo_workspace[pkg].name.as_str();
for target in cargo_workspace[pkg].targets.iter() {
let kind = &cargo_workspace[*target].kind;
}
}
Sysroot
use project_model::Sysroot;
Secondary crates
ra_ap_ide_completion — Completion engine
Used internally by ide::Analysis::completions(). Direct use gives more control:
use ide_completion::{completions, CompletionConfig, CompletionItem};
ra_ap_ide_ssr — Structural search and replace
Pattern-based code transformation with semantic awareness:
use ide_ssr::{MatchFinder, SsrRule};
let rule: SsrRule = "$a.foo($b) ==>> bar($a, $b)".parse()?;
let mut finder = MatchFinder::in_context(&db, &sema, resolve_context, selections);
finder.add_rule(rule)?;
let matches = finder.matches();
let edits = finder.edits();
Wildcards: $name matches any expression/pattern/type and binds it for the replacement template.
ra_ap_hir_def — Definition-level IR
Lower-level than hir. You rarely use this directly, but it's useful to understand:
- ID types:
FunctionId, StructId, EnumId, TraitId, ModuleDefId, AdtId, MacroId
- DefMap: Module-level name resolution results
- ItemTree: Syntax-independent representation of items in a file (survives reparsing if structure unchanged)
- GenericParams: Generic parameter lists with bounds
- Visibility: Pub/restricted visibility
ra_ap_hir_ty — Type inference and trait solving
The type system engine. Again, rarely used directly (go through hir::Type):
Ty / TyKind: Core type representation (uses rustc_type_ir types)
InferenceResult: Per-function type inference results (type of every expr/pat)
TraitRef: A trait reference with substitutions
- Method resolution:
iterate_method_candidates() with autoderef
- Coercion/unification:
could_coerce(), could_unify()
ra_ap_cfg — #[cfg(...)] evaluation
use cfg::{CfgExpr, CfgAtom, CfgOptions};
let expr = CfgExpr::parse(&attr);
let mut opts = CfgOptions::default();
opts.insert_atom("unix".into());
opts.insert_key_value("feature".into(), "foo".into());
let enabled: bool = opts.check(&expr) != Some(false);
ra_ap_parser — Token-to-tree parser
The parser itself, separate from the syntax tree. Used by ra_ap_syntax internally:
use parser::{TopEntryPoint, PrefixEntryPoint, Edition};
let output = TopEntryPoint::SourceFile.parse(&input, Edition::Edition2024);
let reparser = Reparser::for_node(syntax_kind, first_child, parent);
ra_ap_paths — Type-safe path handling
use paths::{AbsPathBuf, AbsPath, RelPathBuf, RelPath};
let abs = AbsPathBuf::assert_utf8(std::env::current_dir()?);
let joined: AbsPathBuf = abs.join("src/lib.rs");
let normalized = abs.normalize();
let rel = RelPathBuf::try_from("src/lib.rs")?;
ra_ap_proc_macro_api — Proc-macro client
Communicates with an external proc-macro-srv process to expand procedural macros:
use proc_macro_api::{ProcMacroClient, ProcMacroKind, MacroDylib};
let client = ProcMacroClient::spawn(&server_path, &env, toolchain.as_ref(), num_processes)?;
let dylib = MacroDylib::new(path);
let macros = client.load_dylib(dylib)?;
for mac in ¯os {
let name = mac.name();
let kind = mac.kind();
}
Cargo dependency setup
All crates are published at the same version. Pin to an exact version — these are unstable internal APIs that break between releases:
[dependencies]
ra_ap_ide = "=0.0.330"
ra_ap_hir = "=0.0.330"
ra_ap_syntax = "=0.0.330"
ra_ap_ide_db = "=0.0.330"
ra_ap_vfs = "=0.0.330"
ra_ap_load-cargo = "=0.0.330"
ra_ap_project-model = "=0.0.330"
Important: All ra_ap_* crates in your dependency tree must be the same version. Mixing versions will cause trait mismatch errors at compile time.
Practical notes
Which crate to start with
- Just parsing?
ra_ap_syntax alone. No database, no Cargo loading.
- Semantic analysis of a Cargo project?
ra_ap_load_cargo + ra_ap_ide (or ra_ap_hir for lower-level access).
- Custom IDE features?
ra_ap_ide for the full feature set.
- Batch diagnostics/stats? Look at
ra_ap_rust-analyzer's cli module for examples.
The Salsa incremental computation model
All semantic queries go through Salsa. This means:
- Queries are memoized and invalidated incrementally when inputs change
Analysis snapshots are cheap (just an Arc bump)
- Long-running queries can be cancelled via
Cancellable<T> return types
- LRU caches control memory usage (
RootDatabase::new(lru_cap))
Error resilience
- The parser always produces a tree, even for invalid code
- Semantic analysis gracefully handles missing/broken definitions
- Most API methods return
Option or Cancellable<Option<T>>
The ra_ap_rust-analyzer crate itself
This is the LSP server binary, not the programmatic API. Its public surface is minimal:
main_loop() — runs the LSP server
server_capabilities() — LSP capability advertisement
cli module — batch-processing subcommands (analysis-stats, diagnostics, SCIP/LSIF generation, SSR)
config module — server configuration
tracing module — logging setup
The cli module is useful as reference code for how to use the API crates in batch mode. Key examples:
cli::analysis_stats — loads a project and runs type inference on every function
cli::diagnostics — runs all diagnostics on a project
cli::scip / cli::lsif — generates code intelligence indexes
cli::ssr — runs structural search-replace from the command line