Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Threading and Safe UI Updates

When the user searches for a city in the Weather App, the application sends a request to the Open-Meteo API to fetch weather data.

Network requests take time. If the request runs directly in the GTK main thread, the entire UI will freeze until the response arrives.

To prevent this, the Weather App uses background threads.

Why Threading Is Needed

GTK applications run using a single main UI thread.

This thread is responsible for:

  • drawing the window
  • updating widgets
  • processing clicks
  • handling events

If a slow operation runs on this thread, the UI cannot update.

For example:

#![allow(unused)]
fn main() {
let result = fetch_open_meteo(&query);
}

If this code runs directly when a button is clicked, the application will freeze until the network request finishes.

During this time:

  • the window cannot be resized
  • buttons do not respond
  • the interface appears stuck

To avoid this problem, long operations must run in separate threads.

Running the Weather Request in a Thread

The Weather App runs the API request inside a background thread using Rust’s thread::spawn.

#![allow(unused)]
fn main() {
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    let res = fetch_open_meteo(&query);
    let _ = tx.send(res);
});
}

This code does three things:

  1. Creates a channel (tx, rx) for communication
  2. Starts a new background thread
  3. Runs the weather request inside that thread

When the request finishes, the result is sent back through the channel.

Because the network request runs in another thread, the UI remains responsive.

Why the move Keyword Is Used

The thread uses this closure:

#![allow(unused)]
fn main() {
move || {
    ...
}
}

The move keyword transfers ownership of variables into the thread.

This is required because the thread may run longer than the current scope. Rust prevents unsafe borrowing by forcing the data to be moved into the thread.

Sometimes data must be cloned before spawning the thread:

#![allow(unused)]
fn main() {
let q_clone = query.clone();
}

Then q_clone is moved into the thread.

This prevents data races and keeps memory safe.

Why Channels Are Used

Background threads should not update GTK widgets directly.

GTK requires all UI updates to happen on the main thread.

So the worker thread only sends the result:

#![allow(unused)]
fn main() {
tx.send(res);
}

The main thread receives the data and updates the UI safely.

This separation prevents crashes and undefined behavior.

Receiving the Result in the Main Thread

To receive results safely, the Weather App checks the channel using:

#![allow(unused)]
fn main() {
glib::timeout_add_local(Duration::from_millis(80), move || {
    match rx.try_recv() {
        ...
    }
});
}

This function runs inside the GTK main event loop.

It periodically checks:

  • whether the background thread has finished
  • whether new data is available

If data is ready, the UI can be updated.

If not, the loop continues waiting.

This approach keeps the interface responsive.

Updating the UI Safely

When the result is received, the main thread updates the interface.

Example:

#![allow(unused)]
fn main() {
apply_weather(name, now, rows);
}

Inside that function, widgets are updated:

#![allow(unused)]
fn main() {
city_title.set_text(&format!("Weather in {name}"));
now_temp.set_text(&format!("Now: {:.1} °C", now));
}

This is safe because it runs in the GTK main thread.

The worker thread never touches UI widgets directly.

Handling Errors

If the weather request fails, the UI can also show an error message.

Example:

#![allow(unused)]
fn main() {
show_error(e);
}

This may update labels or hide forecast widgets.

Again, this happens only in the main thread.

Architecture of the Weather Fetch System

The Weather App separates responsibilities clearly:

User searches city
        ↓
Background thread fetches weather
        ↓
Result sent through channel
        ↓
Main GTK thread receives data
        ↓
UI is updated

Each component has a single responsibility:

  • Thread → performs slow work
  • Channel → transfers data safely
  • Main thread → updates the UI

This architecture keeps the application stable and responsive.

What Freshers Should Learn

From this design, beginners learn several important ideas:

  • GTK uses a single UI thread
  • Slow operations must run in background threads
  • Rust threads are created using thread::spawn
  • The move keyword transfers ownership safely
  • Threads communicate using channels
  • UI updates must happen only in the main thread