Open-Source Wikis

/

Zed

/

How to contribute

/

Patterns and conventions

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 .rules file 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 match for custom logic.
  • No mod.rs. Use src/some_module.rs instead of src/some_module/mod.rs. New crates set [lib] path = "..." in Cargo.toml to keep file names descriptive (gpui.rs, not lib.rs).

  • One file per logical component. Don't fragment functionality into many small files. Existing files first.

  • Full words for variable names. No q for queue, no cnt for count.

  • 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 inside entity.update(cx, |this, cx| …). Dereferences to App.
  • cx: &mut AsyncApp — provided by cx.spawn. Held across .await.
  • cx: &mut AsyncWindowContextcx.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 cx provided by closures. Do not capture the outer cx by reference inside entity.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:

  1. Await it.
  2. Detach: task.detach() or task.detach_and_log_err(cx).
  3. 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 on cx.notify() from another entity.
  • cx.subscribe(other, |this, other, event, cx| …) runs on emitted events.
  • cx.emit(event) requires impl 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::Result is the workhorse for application code.
  • thiserror for libraries with strongly-typed errors.
  • ? for propagation. .context("...") to add a layer.
  • .log_err() (from util::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 MySettings and impl Settings on it.
  • Register in init_settings for the relevant area.
  • Read with MySettings::get(cx) or MySettings::get_global(cx) (registers an observer to cx.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 raw cargo clippy. The script applies workspace-wide -D flags 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 in script/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.

Patterns and conventions – Zed wiki | Factory