Common errors and solutions for GPUI development. Use when debugging build errors, runtime panics, borrow checker issues, or unexpected behavior.
This skill covers common errors and solutions when developing with GPUI.
Error: Cannot borrow cx as mutable more than once
Solution: Use the inner cx provided to closures, not the outer one
// ❌ WRONG
entity.update(cx, |view, inner_cx| {
view.count += 1;
cx.notify(); // Using outer cx - ERROR!
});
// ✅ CORRECT
entity.update(cx, |view, inner_cx| {
view.count += 1;
inner_cx.notify(); // Using inner cx
});
Error: Use of moved value in async block
Solution: Clone before moving into async block
// ❌ WRONG
cx.spawn(async move |this, cx| {
self.data.do_something(); // ERROR: self moved
});
// ✅ CORRECT
let data = self.data.clone();
cx.spawn(async move |this, cx| {
data.do_something();
});
Error: already mutably borrowed: BorrowError
Cause: Trying to update an entity while it's already being updated
Solution: Avoid nested entity updates
// ❌ WRONG - will panic
entity.update(cx, |view, cx| {
entity.update(cx, |view2, cx2| {
// Nested update - PANIC!
});
});
// ✅ CORRECT - restructure logic
entity.update(cx, |view, cx| {
view.prepare_update();
});
// Update happens after first update completes
entity.update(cx, |view, cx| {
view.apply_update();
});
Error: Panic when calling methods on None
Solution: Always check upgrade() result
// ❌ WRONG
let entity = weak.upgrade().unwrap(); // May panic!
// ✅ CORRECT
if let Some(entity) = weak.upgrade() {
entity.update(cx, |view, cx| {
// ...
});
} else {
// Entity was dropped
}
// ✅ ALSO CORRECT (in async context)
this.update(&mut *cx, |view, cx| {
// ...
})?; // Propagate error if entity gone
Error: run_until_parked() completes but test fails
Cause: Using smol::Timer instead of GPUI timers
Solution: Use GPUI executor timers
// ❌ WRONG
smol::Timer::after(Duration::from_secs(1)).await;
// ✅ CORRECT
cx.background_executor.timer(Duration::from_secs(1)).await;
Error: Async work doesn't complete
Cause: Task dropped without being awaited or detached
Solution: Either detach or store the task
// ❌ WRONG - task dropped immediately
cx.spawn(async move |this, cx| {
// Work...
Ok(())
});
// ✅ CORRECT - detach
cx.spawn(async move |this, cx| {
// Work...
Ok(())
}).detach();
// ✅ ALSO CORRECT - store
self.current_task = Some(cx.spawn(async move |this, cx| {
// Work...
Ok(())
}));
Error: Cannot dereference cx in async context
Solution: Use &mut *cx to dereference
// ❌ WRONG
cx.spawn(async move |this, cx| {
this.update(cx, |view, cx| { // ERROR!
// ...
});
});
// ✅ CORRECT
cx.spawn(async move |this, cx| {
this.update(&mut *cx, |view, cx| {
// ...
})?;
Ok(())
});
Error: the trait IntoElement is not implemented for ...
Solution: Ensure type implements IntoElement or use .child() correctly
// ❌ WRONG
div().child(some_struct); // Error if SomeStruct doesn't impl IntoElement
// ✅ CORRECT - use entity
let entity = cx.new(|_| SomeStruct::new());
div().child(entity);
// ✅ ALSO CORRECT - implement RenderOnce
#[derive(IntoElement)]
struct SomeStruct;
impl RenderOnce for SomeStruct {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
div().child("Content")
}
}
Error: Method signature doesn't match trait
Solution: Ensure correct signature with Window and Context<Self>
// ❌ WRONG
impl Render for MyView {
fn render(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
// Missing window parameter
}
}
// ✅ CORRECT
impl Render for MyView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child("Content")
}
}
Symptom: State changes but UI doesn't update
Solution: Call cx.notify() after state changes
// ❌ WRONG
fn increment(&mut self, cx: &mut Context<Self>) {
self.count += 1;
// Missing cx.notify() - UI won't update!
}
// ✅ CORRECT
fn increment(&mut self, cx: &mut Context<Self>) {
self.count += 1;
cx.notify(); // Trigger re-render
}
Symptom: Events stop being received
Solution: Store Subscription in a field
// ❌ WRONG
impl Parent {
fn new(cx: &mut Context<Self>) -> Self {
let child = cx.new(|_| Child::new());
cx.subscribe(&child, |this, _child, event, cx| {
// Handle event
}); // Subscription dropped here!
Self { child }
}
}
// ✅ CORRECT
struct Parent {
child: Entity<Child>,
_subscription: Subscription, // Store to keep alive
}
impl Parent {
fn new(cx: &mut Context<Self>) -> Self {
let child = cx.new(|_| Child::new());
let subscription = cx.subscribe(&child, |this, _child, event, cx| {
// Handle event
});
Self {
child,
_subscription: subscription,
}
}
}
Error: Cannot find type/trait in this scope
Solution: Import from gpui::* or specific module
// Add at top of file
use gpui::*;
// Or specific imports
use gpui::{
div, rgb, px,
App, Context, Entity, Window,
Render, IntoElement, RenderOnce,
};
Use project's clippy script:
# ❌ WRONG
cargo clippy
# ✅ CORRECT (for Zed-based projects)
./script/clippy
Error: Panic from vector indexing
Solution: Use safe access methods
// ❌ WRONG
let item = self.items[index]; // May panic!
// ✅ CORRECT
if let Some(item) = self.items.get(index) {
// Use item
}
// ✅ ALSO CORRECT
if index < self.items.len() {
let item = &self.items[index];
}
Error: called unwrap() on a None value
Solution: Never use unwrap() - use ? or proper error handling
// ❌ WRONG
let value = option.unwrap(); // May panic!
let result = fallible_op().unwrap(); // May panic!
// ✅ CORRECT - propagate error
let value = option.ok_or_else(|| anyhow::anyhow!("Missing value"))?;
let result = fallible_op()?;
// ✅ ALSO CORRECT - handle explicitly
match option {
Some(value) => {
// Use value
}
None => {
// Handle missing case
}
}
| Issue | Cause | Solution |
|---|---|---|
| Multiple borrow | Using outer cx in closure |
Use inner cx |
| Update panic | Nested entity updates | Avoid nesting, restructure |
| UI not updating | Missing cx.notify() |
Call after state changes |
| Task not running | Task dropped | Use .detach() or store task |
| Test timeout | Using smol::Timer |
Use GPUI timer() |
| Events not received | Subscription dropped | Store in field |
| Weak entity panic | Not checking upgrade() |
Use if let Some(...) or ? |
| Unwrap panic | Calling .unwrap() |
Use ? or proper error handling |
cxcx.notify() after state changesSubscription values in struct fieldssmol::Timer with cx.background_executor.timer()? for error propagation or explicit error handling