zed-industries/zed
Patterns and conventions
This page distills the conventions you will see in the Zed codebase. The canonical source is the repo-root .rules file (also exposed as AGENTS.md/CLAUDE.md/GEMINI.md symlinks). Where this page paraphrases, the .rules file wins.
Rust style
Correctness over speed. The
.rulesfile states this explicitly: prioritize clarity over micro-optimization unless the task is performance-critical.No panic-on-error. Avoid
unwrap(). Use?to propagate. For places where you can ignore an error but want visibility, use.log_err()(util::ResultExt).Never silently discard errors.
let _ = fallible(...)is banned. Prefer:fallible(...)?to propagate.fallible(...).log_err()to log-and-swallow.- Explicit
matchfor custom logic.
No
mod.rs. Usesrc/some_module.rsinstead ofsrc/some_module/mod.rs. New crates set[lib] path = "..."inCargo.tomlto keep file names descriptive (gpui.rs, notlib.rs).One file per logical component. Don't fragment functionality into many small files. Existing files first.
Full words for variable names. No
qforqueue, nocntforcount.Variable shadowing for async clones. Re-bind clones inside the async closure to scope the lifetime narrowly:
executor.spawn({ let task_ran = task_ran.clone(); async move { *task_ran.borrow_mut() = true; } });Comment "why," not "what." The codebase deliberately avoids "summary comments" that restate the next two lines of code.
GPUI conventions
The full GPUI primer is in .rules and in the gpui crate's README. The high-level rules:
Context
cx: &mut App— root context.cx: &mut Context<T>— context insideentity.update(cx, |this, cx| …). Dereferences toApp.cx: &mut AsyncApp— provided bycx.spawn. Held across.await.cx: &mut AsyncWindowContext—cx.spawn_in(window, …).
When a function takes callbacks, cx comes after them. When it also takes window, window comes before cx.
Borrows
- Use the inner
cxprovided by closures. Do not capture the outercxby reference insideentity.update(cx, |this, cx| { … }). - Never re-enter
entity.update(cx, …)for an entity that is already being updated higher up the stack. It panics.
Tasks
cx.spawn(async move |cx| …) and cx.background_spawn(async move { … }) return Task<R>. If a task is dropped, its work is cancelled. Three options:
- Await it.
- Detach:
task.detach()ortask.detach_and_log_err(cx). - Store it in a struct field so it lives as long as the entity.
Task::ready(value) constructs a no-op task that immediately yields value.
Render
Implement Render on entities you want to display. Implement RenderOnce plus #[derive(IntoElement)] for stateless components. Style methods are Tailwind-flavored: div().border_1().rounded_md().px_2().bg(theme.background).
.when(cond, |this| …) and .when_some(opt, |this, val| …) keep conditional construction tidy.
Events and notifications
cx.notify()re-renders.cx.observe(other, |this, other, cx| …)runs oncx.notify()from another entity.cx.subscribe(other, |this, other, event, cx| …)runs on emitted events.cx.emit(event)requiresimpl EventEmitter<EventType> for SelfType {}.
Subscriptions return a Subscription value. Drop = unsubscribe. Typically these are stored in _subscriptions: Vec<Subscription> on the owning entity.
Actions
Define small no-data actions with actions!:
actions!(my_namespace, [DoTheThing, AlsoDoThis]);For data-carrying actions, use #[derive(Action)]. Doc comments on actions are shown to the user (in command palette and keymap docs).
Dispatch from code:
window.dispatch_action(DoTheThing.boxed_clone(), cx);Handle on elements:
.on_action(|action: &DoTheThing, window, cx| { … })For listeners that update self, use cx.listener:
.on_click(cx.listener(|this, event, window, cx| { … }))Error handling
anyhow::Resultis the workhorse for application code.thiserrorfor libraries with strongly-typed errors.?for propagation..context("...")to add a layer..log_err()(fromutil::ResultExt) to log-and-discard for places where the operation is best-effort.- Errors should bubble up to user-visible UI. Don't quietly drop a server-call error if the user expects feedback.
Settings and theming
Settings flow through crates/settings:
- Define a
pub struct MySettingsand implSettingson it. - Register in
init_settingsfor the relevant area. - Read with
MySettings::get(cx)orMySettings::get_global(cx)(registers an observer tocx.notify()your view when settings change).
JSON keys are derived; the schema is generated by crates/schema_generator for the in-app settings UI.
Build and lints
- Run
./script/clippy, never rawcargo clippy. The script applies workspace-wide-Dflags and feature toggles. - Workspace lints are pinned in the root
Cargo.toml. - License vetting (
cargo-about) is enforced in CI. New deps may need an entry inscript/licenses/zed-licenses.toml.
PR hygiene (recap)
- Imperative, capitalized, no trailing punctuation. e.g.
Fix crash in project panel. - Optional crate-name prefix:
git_ui: Add history view. - Always end the body with a
Release Notes:section. - Add a screenshot/recording for UI changes.
- One concern per PR.
Crate naming
- snake_case directory names, snake_case package names.
- Application binaries are at the top of
crates/(e.g.zed,cli,collab,remote_server). - "Library + UI" pairs are split:
dap+debugger_ui,agent+agent_ui,git+git_ui,extension+extensions_ui,settings+settings_ui. The pattern lets the headless logic ship without GUI dependencies.
Telemetry and crashes
If you add a new event, route it through crates/telemetry_events so its schema is checked. Crash investigation is documented in .rules and reproduced in Debugging.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.