| name | rust-coding-guidelines |
| description | Rust and GPUI coding standards. MUST LOAD before writing any Rust code. Covers error handling, naming conventions, async patterns, file organization, and GPUI-specific rules. This skill is MANDATORY for all Rust work in this project. |
Rust Coding Guidelines
IMPORTANT: This skill contains mandatory coding rules for this project. Violations will cause code review failures.
Core Principles
- Prioritize correctness and clarity - Speed/efficiency are secondary unless specified
- Avoid creative additions - Unless explicitly requested
- Prefer existing files - Unless creating a new logical component
- Comments explain "why", not "what" - No organizational/summary comments
Error Handling (CRITICAL)
NEVER use unwrap()
let value = option.unwrap();
let result = operation().unwrap();
let value = option.ok_or_else(|| anyhow::anyhow!("Missing value"))?;
let result = operation()?;
match option {
Some(value) => { }
None => { }
}
NEVER silently discard errors
let _ = client.request(url).await?;
client.request(url).await?;
operation().log_err();
if let Err(e) = operation() {
eprintln!("Operation failed: {}", e);
}
ALWAYS propagate errors to UI
fn save_file(&mut self, cx: &mut Context<Self>) {
cx.spawn(async move |this, cx| {
match write_file(path, data).await {
Ok(()) => {
this.update(&mut *cx, |view, cx| {
view.status = "Saved successfully".into();
cx.notify();
})?;
}
Err(e) => {
this.update(&mut *cx, |view, cx| {
view.error = Some(format!("Save failed: {}", e));
cx.notify();
})?;
}
}
Ok(())
}).detach_and_log_err(cx);
}
Indexing Safety
Be careful with indexing operations - they may panic if indexes are out of bounds.
let item = items[index];
let item = items.get(index).ok_or_else(|| anyhow::anyhow!("Index out of bounds"))?;
Variable Naming
Use full words (NO abbreviations)
let q = VecDeque::new();
let cnt = 0;
let btn = Button::new();
let queue = VecDeque::new();
let count = 0;
let button = Button::new();
Async Patterns
Variable Shadowing for Clones
Use variable shadowing to scope clones in async contexts:
executor.spawn({
let task_ran = task_ran.clone();
async move {
*task_ran.borrow_mut() = true;
}
});
File Organization
NEVER create mod.rs files
// WRONG
src/
components/
mod.rs // Don't do this
button.rs
// CORRECT
src/
components.rs // Module file
components/
button.rs
Library Root Paths
For crates, specify library root in Cargo.toml:
[lib]
path = "src/my_lib.rs"
GPUI-Specific Rules
Context Types
App - root context, access to global state
Context<T> - provided when updating Entity<T>, dereferences to App
AsyncApp / AsyncWindowContext - from cx.spawn, can be held across await points
Window Parameter
Window comes before cx when present:
impl Render for MyView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
}
}
Entity Operations
let view = entity.read(cx);
entity.update(cx, |view, inner_cx| {
view.count += 1;
inner_cx.notify();
});
entity.update(cx, |view, cx| {
entity.update(cx, |_, _| {});
});
Testing Timers
In GPUI tests, use GPUI executor timers, NOT smol::Timer:
smol::Timer::after(duration).await;
cx.background_executor().timer(duration).await;
cx.background_executor.timer(duration).await;
Event Handlers
.on_click(cx.listener(|this: &mut Self, event, window, cx| {
}))
Notify After State Changes
fn update_state(&mut self, cx: &mut Context<Self>) {
self.data = new_data;
cx.notify();
}
EventEmitter
impl EventEmitter<MyEvent> for MyView {}
cx.emit(MyEvent::ValueChanged(value));
Subscriptions
Store Subscription in struct fields - they auto-cancel when dropped:
struct Parent {
child: Entity<Child>,
_subscription: Subscription,
}
Project-Specific Rules
dbg!() and todo!() are DENIED
This project has Clippy rules that deny dbg!() and todo!() macros.
dbg!(value);
todo!();
Workspace Dependencies
All dependencies must be declared in workspace Cargo.toml, sub-crates use .workspace = true:
[dependencies]
gpui.workspace = true
Quick Reference Checklist
Before submitting code:
References