| name | gpui-code-quality |
| description | Best practices and code quality guidelines for GPUI development. Use when refactoring, reviewing code, or ensuring adherence to GPUI idioms. |
GPUI Code Quality
This skill covers best practices and code quality guidelines for GPUI development.
Error Handling
Never Use unwrap()
Rule: Avoid unwrap() and panic-inducing methods
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
Rule: Don't use let _ = on fallible operations
let _ = client.request(url).await?;
client.request(url).await?;
operation().log_err();
if let Err(e) = operation() {
eprintln!("Operation failed: {}", e);
}
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);
}
Variable Naming
Use Full Words
Rule: Avoid abbreviations, use descriptive names
let q = VecDeque::new();
let cnt = 0;
let btn = Button::new();
let queue = VecDeque::new();
let count = 0;
let button = Button::new();
Meaningful Names
let x = calculate();
let temp = process(data);
let thing = Entity::new();
let result = calculate();
let processed_data = process(data);
let user_profile = Entity::new();
Async Context Variable Shadowing
Use variable shadowing to scope clones in async contexts:
fn start_work(&mut self, cx: &mut Context<Self>) {
let data = self.data.clone();
let config = self.config.clone();
cx.spawn(async move |this, cx| {
let result = process(data, config).await;
this.update(&mut *cx, |view, cx| {
view.result = result;
cx.notify();
})?;
Ok(())
}).detach();
}
File Organization
Avoid mod.rs Files
Rule: Use src/module.rs instead of src/module/mod.rs
// ❌ 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"
State Management
Call cx.notify() After Changes
fn update_data(&mut self, data: String, cx: &mut Context<Self>) {
self.data = data;
}
fn update_data(&mut self, data: String, cx: &mut Context<Self>) {
self.data = data;
cx.notify();
}
Use WeakEntity for Back-References
struct Child {
parent: Entity<Parent>,
}
struct Child {
parent: WeakEntity<Parent>,
}
Rendering
Keep Render Pure
Rule: Don't mutate state in render() method
impl Render for MyView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.render_count += 1;
div().child(format!("Renders: {}", self.render_count))
}
}
impl Render for MyView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.child(format!("Count: {}", self.count))
.child(
div()
.child("Increment")
.on_click(cx.listener(|this, _event, _window, cx| {
this.count += ;
cx.();
}))
)
}
}
Use SharedString for Text
struct MyView {
title: String,
}
struct MyView {
title: SharedString,
}
Async Patterns
Detach or Store Tasks
cx.spawn(async move |this, cx| {
Ok(())
});
cx.spawn(async move |this, cx| {
Ok(())
}).detach();
self.current_task = Some(cx.spawn(async move |this, cx| {
Ok(())
}));
Use background_spawn for CPU Work
cx.spawn(async move |this, cx| {
let result = expensive_computation();
Ok(())
});
cx.spawn(async move |this, cx| {
let result = cx.background_spawn(async {
expensive_computation()
}).await;
this.update(&mut *cx, |view, cx| {
view.result = result;
cx.notify();
})?;
Ok(())
});
Comments
Only Explain "Why"
Rule: Don't write comments that summarize code
self.counter += 1;
self.title = "Hello".into();
self.update_cache();
cx.notify();
self.count = self.count.saturating_sub(1);
Testing
Use GPUI Timers in Tests
#[gpui::test]
async fn test_delay(cx: &mut TestAppContext) {
smol::Timer::after(Duration::from_secs(1)).await;
}
#[gpui::test]
async fn test_delay(cx: &mut TestAppContext) {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.background_executor.run_until_parked();
}
Build Guidelines
Use Project Scripts
cargo clippy
./script/clippy
cargo build -q
cargo test -q
Code Organization
Prefer Existing Files
Rule: Add functionality to existing files unless it's a new logical component
Module Organization
src/
ui/
components.rs
theme.rs
styles.rs
data/
models.rs
state.rs
Summary of Best Practices
| Rule | Rationale |
|---|
Never use unwrap() | Prevents panics, forces explicit error handling |
Never let _ = on fallible ops | Errors should be handled or logged |
| Use full variable names | Improves readability and maintainability |
| Avoid mod.rs files | Cleaner project structure |
Call cx.notify() after mutations | Ensures UI updates |
Use WeakEntity for back-refs | Prevents memory leaks |
Keep render() pure | Avoids race conditions |
Use SharedString for text | Reduces allocations |
| Detach or store tasks | Prevents cancelled work |
| Use GPUI timers in tests | Prevents test failures |
| Only comment "why", not "what" | Reduces noise, focuses on reasoning |
| Propagate errors to UI | Provides user feedback |
Use background_spawn() for CPU work | Keeps UI responsive |
Checklist for Code Review
References