gpui-troubleshooting
Common errors and solutions for GPUI development. Use when debugging build errors, runtime panics, borrow checker issues, or unexpected behavior.
When & Why to Use This Skill
This Claude skill serves as a comprehensive troubleshooting guide for GPUI development, specifically designed to help developers resolve complex Rust-based issues. It provides actionable solutions for borrow checker errors, async task management, and UI synchronization, enabling faster debugging and more robust code implementation within the GPUI framework.
Use Cases
- Resolving 'Multiple Borrow Errors' by identifying and correcting improper context (cx) usage within entity update closures.
- Fixing UI synchronization issues where state changes are not reflected in the interface due to missing notification calls.
- Debugging asynchronous execution problems, such as tasks being dropped prematurely or using non-GPUI timers in test environments.
- Preventing runtime panics by implementing safe entity upgrading patterns and replacing unsafe unwraps with proper error handling.
- Correcting trait implementation errors for UI components, such as Render and IntoElement signature mismatches.
| name | gpui-troubleshooting |
|---|---|
| description | Common errors and solutions for GPUI development. Use when debugging build errors, runtime panics, borrow checker issues, or unexpected behavior. |
GPUI Troubleshooting
This skill covers common errors and solutions when developing with GPUI.
Borrow Checker Errors
Multiple Borrow Error
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
});
Moved Value Error
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();
});
Entity Errors
Update While Updating Panic
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();
});
WeakEntity Upgrade Failure
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
Async Errors
"Nothing Left to Run" in Tests
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;
Task Dropped Before Completion
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(())
}));
Async Context Borrow Error
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(())
});
Trait Errors
IntoElement Not Implemented
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")
}
}
Render Trait Signature Mismatch
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")
}
}
UI Not Updating
Forgetting cx.notify()
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
}
Subscription Dropped
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,
}
}
}
Build Errors
Missing Imports
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,
};
Clippy Warnings
Use project's clippy script:
# ❌ WRONG
cargo clippy
# ✅ CORRECT (for Zed-based projects)
./script/clippy
Runtime Panics
Index Out of Bounds
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];
}
Unwrap on None/Err
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
}
}
Common Mistakes Summary
| 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 |
Debugging Tips
- Check the inner cx: In update closures, always use the inner
cx - Verify notify calls: Add
cx.notify()after state changes - Store subscriptions: Keep
Subscriptionvalues in struct fields - Use GPUI timers in tests: Replace
smol::Timerwithcx.background_executor.timer() - Avoid unwrap: Use
?for error propagation or explicit error handling - Check task lifecycle: Ensure tasks are detached or stored, not dropped
- Watch for nested updates: Avoid updating entities within update closures