Your First GPUI Application: Windows and the Application Lifecycle

Building high-performance, native user interfaces with Rust can be a deeply rewarding experience, especially when you leverage the GPU for acceleration. In this chapter, we embark on our journey with GPUI, the powerful UI framework powering the Zed editor. You’ll learn the fundamental steps to set up your environment and launch your very first GPUI application, creating a basic window that will serve as the canvas for all your future creations.

This guide is tailored for Rust developers eager to explore the cutting edge of UI development on macOS and Linux. We’ll start with the absolute basics: understanding the core GPUI application lifecycle and how to manage windows. By the end of this chapter, you’ll have a running GPUI application and a solid grasp of how to initiate and manage its foundational elements.

Before we dive in, ensure you have a stable Rust toolchain installed via rustup. If not, please refer to the official Rust installation guide before proceeding.

The Heart of Your App: GPUI’s Application Lifecycle

Every graphical application has a lifecycle, a sequence of events from launch to shutdown. GPUI streamlines this by providing a clear entry point and managing the complexities of interacting with the operating system, handling events, and orchestrating rendering. Understanding this lifecycle is crucial, as it dictates how your UI application starts and runs.

Application and App: The Core Orchestrators

When you launch a GPUI application, two key concepts come into play:

  • gpui::Application: This is the foundational, global entity that represents your entire application process. It’s responsible for the main event loop, which continuously listens for user input (keyboard, mouse), system events (window resize, close), and manages the underlying OS-level interactions. You rarely interact with Application directly, but rather initiate its run method to start everything.
    • Why it exists: To abstract away the complex, platform-specific details of setting up a GUI application and managing its main loop.
  • gpui::App: Once the Application has successfully started, it provides you with an App instance. This App is your primary interface for interacting with the UI framework throughout your application’s lifetime. Through App, you can open new windows, schedule asynchronous tasks, manage global application state, and much more. Think of App as your personal control panel for the running UI.
    • Why it exists: To give developers a high-level, ergonomic way to control and build their UI without needing to delve into the low-level Application details.

📌 Key Idea: You kick off the entire UI process with App::run, and then use the AppContext (which is powered by App) to build and manage your user interface.

The Hybrid Rendering Model: Performance by Design

GPUI employs a sophisticated hybrid immediate and retained mode rendering approach, heavily leveraging the GPU for optimal performance. This isn’t just a technical detail; it’s a core philosophy that influences how you’ll structure your UI code.

  • Immediate Mode Elements: When you define your UI, you’ll often describe elements directly in your rendering code. GPUI processes these descriptions and draws them “immediately” to the screen. This is powerful for highly dynamic UIs that change frequently.
  • Retained Mode Optimizations: Behind the scenes, GPUI also maintains a scene graph—a structured representation of your UI elements. This allows the framework to perform smart optimizations, like only redrawing parts of the screen that have actually changed, rather than the entire window. This efficiency is critical for achieving the smooth, 120fps+ performance Zed is known for.

By combining these modes, GPUI aims for the best of both worlds: the flexibility to create dynamic UIs and the raw speed of GPU acceleration. We’ll explore this in much more detail in later chapters, but it’s important to grasp this fundamental design principle early on.

🧠 Important: Active Development Ahead! GPUI is the cutting-edge UI framework powering the Zed editor and is under active, rapid development. This means its APIs are not yet stable and are subject to frequent changes, including breaking changes. Always be prepared to consult the official zed-industries/zed repository for the most up-to-date patterns and examples. This guide reflects the state of GPUI as of 2026-05-24, but changes may occur.

Setting Up Your First GPUI Project

Let’s get your development environment ready and create a new Rust project.

Step 1: Create a New Rust Project

First, open your terminal and create a new Rust binary project. We’ll call it hello-gpui.

cargo init hello-gpui --bin
cd hello-gpui

This command initializes a new directory hello-gpui with a basic Cargo.toml and src/main.rs file, ready for a binary executable.

Step 2: Add GPUI as a Dependency

Next, we need to tell Rust about our GPUI dependency. Since GPUI is part of the Zed editor’s monorepo and is under active development, we’ll point directly to its main branch on GitHub.

Open your Cargo.toml file and add the following under the [dependencies] section:

# Cargo.toml
[package]
name = "hello-gpui"
version = "0.1.0"
edition = "2021"

[dependencies]
# Using the latest 'main' branch of GPUI as of 2026-05-24.
# Be aware that APIs are unstable and subject to frequent changes.
gpui = { git = "https://github.com/zed-industries/zed", branch = "main", package = "gpui" }

Quick Note: The package = "gpui" is crucial here. The zed-industries/zed repository is a workspace containing multiple crates (like gpui, editor, language, etc.), and this line specifies that we specifically want to depend on the gpui crate from that repository.

Now, run cargo check to ensure your dependencies resolve correctly. This will download and compile GPUI and its dependencies. This might take a few minutes, especially the first time you run it.

cargo check

If cargo check completes without errors, you’re ready to write some code!

Step-by-Step Implementation: Launching Your First Window

Now, let’s write the minimal code to start a GPUI application and open a simple, empty window.

Open src/main.rs and let’s build it up incrementally.

Initializing the Application

First, we need the main entry point: App::run. This function starts the GPUI application loop.

// src/main.rs
fn main() {
    // 1. Initialize the GPUI application.
    // This is the entry point for your UI application.
    // The closure passed to `run` is where you define your application's setup logic.
    gpui::App::run(|cx: &mut gpui::AppContext| {
        // Our application setup will go here.
        // `cx` is our `AppContext`, providing access to global app services.
    });
}
  • gpui::App::run(...): This is the core function that bootstraps your GPUI application. It takes a closure which will be executed once the application environment is ready.
  • |cx: &mut gpui::AppContext|: The closure receives a mutable reference to AppContext. This cx is your primary handle for interacting with the GPUI framework at the application level.

Importing Necessary Types

To open a window, we’ll need several types from the gpui crate. Let’s add the use statements at the top of src/main.rs.

// src/main.rs
use gpui::{App, WindowOptions, WindowBounds, Size, Pixels, Bounds, Point};

fn main() {
    App::run(|cx: &mut gpui::AppContext| {
        // Our application setup will go here.
    });
}
  • App: The main application runner.
  • WindowOptions: A struct to configure how a new window should appear.
  • WindowBounds: Defines the window’s size and position.
  • Size, Pixels, Bounds, Point: These types are used together to specify precise, pixel-based dimensions and coordinates for the window.

Opening Your First Window

Now, inside the App::run closure, we’ll use cx.open_window to create a new operating system window.

// src/main.rs
use gpui::{App, WindowOptions, WindowBounds, Size, Pixels, Bounds, Point};

fn main() {
    App::run(|cx: &mut gpui::AppContext| {
        // 2. Open a new window.
        // `cx.open_window` creates a new top-level window for your application.
        // It takes `WindowOptions` to configure the window's appearance and behavior.
        cx.open_window(
            WindowOptions {
                // Set the title that appears in the window's title bar.
                title: Some("Hello GPUI!".into()),
                // Define the initial size and position of the window.
                bounds: Some(WindowBounds::Fixed(Bounds {
                    origin: Point { x: Pixels(100.0), y: Pixels(100.0) },
                    size: Size { width: Pixels(800.0), height: Pixels(600.0) },
                })),
                // Additional options can be configured here, e.g., if it's a primary window.
                ..Default::default()
            },
            // This closure defines the initial content or "root view" for the window.
            // For now, we'll just return an empty `()` unit type, which GPUI handles gracefully.
            // In future chapters, this is where you'll build your UI components.
            |cx| {
                // This `cx` is a `gpui::ViewContext` specific to this window.
                // We'll use it to create and manage views later.
                // For now, we return `()`, signifying no specific root view created yet.
                Ok(())
            },
        );
    });
}

Let’s break down the new additions:

  • cx.open_window(...): This method on AppContext is how you request the operating system to create a new window. It takes two main arguments:
    1. WindowOptions: A struct to configure the window’s properties.
      • title: Some("Hello GPUI!".into()): Sets the text that appears in the window’s title bar. into() converts the string literal into an owned String. Some indicates that a title is provided.
      • bounds: Some(WindowBounds::Fixed(...)): Specifies the initial size and position.
        • WindowBounds::Fixed(...): Means the window will start at a specific, fixed size and position. Other options might allow for default sizing or full screen.
        • Bounds { origin: Point { ... }, size: Size { ... } }: Defines the actual rectangular area.
          • origin: Point { x: Pixels(100.0), y: Pixels(100.0) }: Sets the top-left corner of the window. Pixels(value) is a GPUI type for specifying dimensions in device-independent pixels.
          • size: Size { width: Pixels(800.0), height: Pixels(600.0) }: Sets the width and height of the window.
      • ..Default::default(): This Rust syntax is a handy way to fill in any remaining fields of WindowOptions with their default values, ensuring you only specify what you need to customize.
    2. |cx| { Ok(()) }: This is a second closure passed to open_window. It receives a gpui::ViewContext. This context is specific to the content area of this particular window. It’s where you would typically create and manage the visual “views” (UI components) that will populate your window. For now, we’re returning Ok(()), indicating that we’re not yet putting any visual elements inside our window. It will just be a blank canvas.

Running Your Application

Now, save src/main.rs and run your application from the terminal:

cargo run

You should see a new window pop up on your screen with the title “Hello GPUI!” and the specified dimensions (800x600 pixels, offset by 100 pixels from the top-left of your screen). It will be a blank, empty window, but it’s your GPU-accelerated window, powered by Rust and GPUI!

Congratulations! You’ve successfully launched your first GPUI application. This blank window is more than just an empty rectangle; it’s your canvas for building rich, high-performance user interfaces.

Mini-Challenge: Customize Your Window’s Appearance

Now that you have a basic understanding of how to launch a GPUI application and create a window, let’s try a small modification to solidify your grasp of WindowOptions.

Challenge: Modify your hello-gpui application to open a window that is:

  1. Titled “My Custom GPUI App”.
  2. Positioned at (x: 200.0, y: 200.0) pixels.
  3. Sized (width: 1024.0, height: 768.0) pixels.

Hint: Review the WindowOptions struct and the Bounds, Point, and Size types. Remember to use Pixels() for all dimension values.

What to observe/learn: After making your changes, run cargo run again. Pay attention to how quickly cargo compiles and launches your app for small code modifications. This rapid feedback loop is crucial for efficient UI development.

Common Pitfalls & Troubleshooting

Working with an actively developed framework like GPUI can sometimes present unique challenges. Knowing what to look for can save you a lot of time.

⚠️ What can go wrong: Dependency Resolution Issues

Problem: cargo build or cargo run fails with errors like “failed to select a version for gpui” or “no matching package named gpui found.”

Cause:

  • Incorrect git URL or branch in Cargo.toml.
  • The main branch of Zed might have undergone a breaking change that impacts how GPUI is structured within the monorepo, or the package name might have changed.
  • Network issues preventing Git from fetching the repository.

Solution:

  1. Double-check Cargo.toml: Ensure the git URL, branch, and package name are exactly as specified:
    gpui = { git = "https://github.com/zed-industries/zed", branch = "main", package = "gpui" }
  2. Clean Cargo Cache: Sometimes old dependency metadata can cause issues. Try cleaning your Cargo cache and updating dependencies:
    cargo clean
    cargo update
    cargo run
  3. Verify Zed Repository Structure: If problems persist, visit the Zed GitHub repository and navigate to crates/gpui. Confirm that the gpui crate still exists at that path and that main is indeed the correct development branch.

⚠️ What can go wrong: Platform-Specific Build Errors

Problem: On Linux, you might encounter errors related to missing X11 or Wayland development libraries (e.g., pkg-config errors, “linker cc not found” for specific libraries).

Cause: GPUI, being a native UI framework, relies on underlying operating system graphics libraries to function. On Linux, this typically means X11 or Wayland development headers and related tools.

Solution: Install the necessary development packages for your Linux distribution.

  • Ubuntu/Debian-based systems:
    sudo apt install libxkbcommon-dev libfontconfig1-dev libssl-dev pkg-config
  • Fedora-based systems:
    sudo dnf install libxkbcommon-devel fontconfig-devel openssl-devel pkg-config
  • Arch Linux-based systems:
    sudo pacman -S libxkbcommon fontconfig openssl pkg-config
    (These are common dependencies; specific error messages might indicate additional packages are needed.)

⚠️ What can go wrong: Unstable API Changes

Problem: Your code that compiled yesterday now fails to compile today with errors like “no method named open_window found” on AppContext or “expected AppContext, found &mut gpui::AppContext”.

Cause: This is the most common pitfall when working with a framework in active development. GPUI’s APIs are not yet stable and can change frequently as the Zed team refines the framework. The main branch can be updated at any time.

Solution:

  1. Consult Zed’s Source: The most authoritative and up-to-date source for current GPUI usage is the Zed editor’s own source code.
    • Browse the zed-industries/zed repository on GitHub, specifically the crates/gpui directory.
    • Look at how Zed itself uses GPUI in its main.rs or other UI-related files within the editor’s source. This will show you the latest API signatures and best practices.
  2. Check gpui-tutorial: The hedge-ops/gpui-tutorial repository (linked in the research brief) can also be a helpful reference, but remember it might lag behind the main branch.
  3. Adapt and Learn: Embrace the need to adapt your code. This is part of working with cutting-edge technology. The errors, while frustrating, are opportunities to learn the evolving patterns and the framework’s design philosophy.

Real-world insight: For projects using rapidly evolving open-source frameworks, it’s common practice to dedicate time to “dependency management” and “API adaptation” as a regular part of the development cycle. Automated CI/CD pipelines often include steps to detect and report breaking changes early.

Summary

In this chapter, you’ve taken your first significant steps into the world of GPUI development. You’ve established the foundation for all future UI creations.

Here are the key takeaways:

  • You initialized a new Rust project and successfully added GPUI as a git dependency, specifically pointing to the main branch of the Zed repository (as of 2026-05-24).
  • You learned that App::run is the core entry point for any GPUI application, taking a closure that provides an AppContext for global application services.
  • You used AppContext::open_window to create a new operating system window, configuring its title, size, and position using WindowOptions, Bounds, Point, and Size with Pixels.
  • You successfully launched a blank GPUI window, confirming your development setup and basic understanding of the application lifecycle.
  • You are now aware of common pitfalls, such as unstable APIs and platform-specific dependencies, and understand the importance of consulting the Zed source code for the most current usage patterns.

This blank window is more than just an empty rectangle; it’s your canvas for building rich, high-performance user interfaces. In the next chapter, we’ll start populating this window with actual UI elements, introducing the concept of views and how GPUI renders them.

References

This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.