Overview
An Entity<T> is a handle to state of type T, providing safe access and updates.
Key Methods:
entity.read(cx)→&T- Read-only accessentity.read_with(cx, |state, cx| ...)→R- Read with closureentity.update(cx, |state, cx| ...)→R- Mutable updateentity.downgrade()→WeakEntity<T>- Create weak referenceentity.entity_id()→EntityId- Unique identifier
Entity Types:
Entity<T>: Strong reference (increases ref count)WeakEntity<T>: Weak reference (doesn’t prevent cleanup, returnsResult)
Quick Start
Creating and Using Entities
// Create entity
let counter = cx.new(|cx| Counter { count: 0 });
// Read state
let count = counter.read(cx).count;
// Update state
counter.update(cx, |state, cx| {
state.count += 1;
cx.notify(); // Trigger re-render
});
// Weak reference (for closures/callbacks)
let weak = counter.downgrade();
let _ = weak.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
In Components
struct MyComponent {
shared_state: Entity<SharedData>,
}
impl MyComponent {
fn new(cx: &mut App) -> Entity<Self> {
let shared = cx.new(|_| SharedData::default());
cx.new(|cx| Self {
shared_state: shared,
})
}
fn update_shared(&mut self, cx: &mut Context<Self>) {
self.shared_state.update(cx, |state, cx| {
state.value = 42;
cx.notify();
});
}
}
Async Operations
impl MyComponent {
fn fetch_data(&mut self, cx: &mut Context<Self>) {
let weak_self = cx.entity().downgrade();
cx.spawn(async move |cx| {
let data = fetch_from_api().await;
// Update entity safely
let _ = weak_self.update(cx, |state, cx| {
state.data = Some(data);
cx.notify();
});
}).detach();
}
}
Core Principles
Always Use Weak References in Closures
// ✅ Good: Weak reference prevents retain cycles
let weak = cx.entity().downgrade();
callback(move || {
let _ = weak.update(cx, |state, cx| cx.notify());
});
// ❌ Bad: Strong reference may cause memory leak
let strong = cx.entity();
callback(move || {
strong.update(cx, |state, cx| cx.notify());
});
Use Inner Context
// ✅ Good: Use inner cx from closure
entity.update(cx, |state, inner_cx| {
inner_cx.notify(); // Correct
});
// ❌ Bad: Use outer cx (multiple borrow error)
entity.update(cx, |state, inner_cx| {
cx.notify(); // Wrong!
});
Avoid Nested Entity Updates
Nested entity.update(cx, …) calls are dangerous. The default posture is: do not nest them. The sub-cases below clarify when a panic is guaranteed vs. merely possible.
Same entity → always panics.
GPUI locks an entity for the entire duration of its update or render pass. Re-entering that same lock panics immediately:
cannot update … while it is already being updated
// ❌ Panic: updating entity_a from inside entity_a's own update
entity_a.update(cx, |state, cx| {
entity_a.update(cx, |_, _| {}); // PANIC — same lock
});
Different entity → generally safe, but indirect cycles still panic.
Each entity has its own lock, so updating entity_b from within entity_a’s update normally succeeds. However, if entity_b’s callback reaches back into entity_a — directly or through a chain — GPUI will attempt to re-acquire entity_a’s lock and panic.
// ✅ Usually fine: different entities, no cycle
entity_a.update(cx, |_, cx| {
entity_b.update(cx, |_, _| {}); // OK — different lock
});
// ❌ Panic: indirect cycle back to entity_a
entity_a.update(cx, |_, cx| {
entity_b.update(cx, |_, cx| {
entity_a.update(cx, |_, _| {}); // PANIC — entity_a is still locked
});
});
When in doubt, flatten the call sequence rather than nesting: finish the outer update, then update the second entity from outside.
defer_in does not bypass the lock. cx.defer_in(window, callback) schedules callback to run on the current entity — meaning GPUI re-acquires the entity’s lock to execute it. The re-entrancy rules apply equally inside the deferred callback:
// ❌ Panic: defer_in re-locks entity_a; calling entity_a.update inside re-enters
impl SomeDelegate for MyAdapter {
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {
cx.defer_in(window, |list_state, window, cx| {
// list_state is locked for this callback!
parent.update(cx, |this, cx| {
this.list.update(cx, |_, _| {}); // PANIC — list is already locked above
});
});
}
}
// ✅ Fix: use the direct &mut reference the callback provides
impl SomeDelegate for MyAdapter {
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {
cx.defer_in(window, |list_state, window, cx| {
// Access list data directly — no entity lock needed
list_state.delegate_mut().some_hook();
// Update the *parent* entity — different lock, safe
parent.update(cx, |this, cx| { /* … */ });
// Sync list state directly after parent update
list_state.delegate_mut().update_snapshot(new_val);
});
}
}
Snapshot pattern for render callbacks. render_item (and any other rendering hook) runs inside the entity’s render pass. It must never call entity.read(cx) or entity.update(cx, …) on any external entity. Instead, keep a plain snapshot field updated eagerly from outside render after every mutation:
// ❌ Panic in render_item — ListState is already locked
fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<ListState<Self>>) -> … {
let checked = parent_entity.read(cx).selection.contains(&ix); // PANIC
}
// ✅ Read from a plain snapshot field — no entity access
fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<ListState<Self>>) -> … {
let checked = self.selection_snapshot.iter().any(|(sel_ix, _)| sel_ix == &ix);
}
Common Use Cases
- Component State: Internal state that needs reactivity
- Shared State: State shared between multiple components
- Parent-Child: Coordinating between related components (use weak refs)
- Async State: Managing state that changes from async operations
- Observations: Reacting to changes in other entities
Reference Documentation
Complete API Documentation
- Entity API: See api-reference.md
- Entity types, methods, lifecycle
- Context methods, async operations
- Error handling, type conversions
Implementation Guides
-
Patterns: See patterns.md
- Model-view separation, state management
- Cross-entity communication, async operations
- Observer pattern, event subscription
- Pattern selection guide
-
Best Practices: See best-practices.md
- Avoiding common pitfalls, memory leaks
- Performance optimization, batching updates
- Lifecycle management, cleanup
- Async best practices, testing
-
Advanced Patterns: See advanced.md
- Entity collections, registry pattern
- Debounced/throttled updates, state machines
- Entity snapshots, transactions, pools