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

CSV Storage and Persistence

The Weather App allows users to save cities such as Berlin, Munich, These cities should remain available even after the application is closed.

To achieve this, the Weather App stores city names in a simple CSV file.

Instead of using a database, the app uses a lightweight text file. This makes the storage system easy to understand and implement, especially for beginners.

CSV File Structure

The storage file is defined using a constant:

#![allow(unused)]
fn main() {
const CSV_FILE: &str = "saved_cities.csv";
}

This file stores one city per line.

Example:

Berlin
Munich
Paris

There are no commas or extra columns. Each line simply represents one city name.

This simple structure makes the file:

  • easy to read
  • easy to debug
  • easy to edit manually if needed

Why a Simple CSV Design Is Used

The Weather App only needs to store city names.

It does not store:

  • latitude
  • longitude
  • timestamps
  • weather data

These values are fetched dynamically from the weather API when needed. Because of this, a simple text file is enough. This design helps beginners understand persistence without needing databases or complex file formats.

In-Memory Storage

While the application is running, the cities are stored in memory using a vector:

Vec<String>

Example:

["Berlin", "Munich", "Paris"]

This vector is the main working data structure used by the application.

The CSV file is only used to save and restore this data when the app starts or closes.

Loading Cities When the App Starts

When the Weather App starts, it reads the CSV file and loads the cities into memory.

#![allow(unused)]
fn main() {
fn load_cities_from_csv() -> Vec<String> {
    if !Path::new(CSV_FILE).exists() {
        return vec![];
    }

    let Ok(text) = fs::read_to_string(CSV_FILE) else {
        return vec![];
    };

    let mut out = Vec::new();

    for line in text.lines() {
        let city = line.trim();

        if !city.is_empty() &&
           !out.iter().any(|c| c.eq_ignore_ascii_case(city)) {
            out.push(city.to_string());
        }
    }

    out
}
}

This function performs several steps. First, it checks if the file exists. If the file does not exist, the application simply starts with an empty list. Next, the file is read as plain text. Each line represents a city. The function removes extra spaces and avoids duplicates before adding cities to the vector. At the end, the function returns the list of cities stored in memory.

Saving Cities After Changes

Whenever a user adds or removes a city, the CSV file must be updated.

The Weather App saves the cities using this function:

#![allow(unused)]
fn main() {
fn save_cities_to_csv(cities: &Vec<String>) {
    let mut buf = String::new();

    for c in cities {
        buf.push_str(c);
        buf.push('\n');
    }

    let _ = fs::write(CSV_FILE, buf);
}
}

This function converts the vector of cities into plain text. Each city is written on its own line. The entire file is rewritten every time the list changes. This approach keeps the logic simple because the file always matches the current state of the vector.

How Storage Works in the Weather App

The Weather App follows a simple data flow.

1. Application Start

The CSV file is read and converted into a vector.

CSV file → Vec<String>

2. User Adds or Removes Cities

The vector is updated.

Vec<String> updated

3. File Is Updated

The vector is written back to the CSV file.

Vec<String> → CSV file

This means:

  • The vector in memory is the active state
  • The CSV file only stores the data permanently

Connection to the Sidebar

The sidebar list in the Weather App displays the cities stored in the vector.

When a user adds a city:

  1. The city is added to the vector
  2. The CSV file is updated
  3. The sidebar list is rebuilt

When a user deletes a city:

  1. The city is removed from the vector
  2. The CSV file is updated
  3. The sidebar refreshes

This keeps the UI and storage synchronized.

Why This Design Is Good for Beginners

This storage system is intentionally simple.

It teaches beginners how persistence works without introducing complex technologies.

From this example, a beginner learns how to:

  • store application data in a file
  • read file content at startup
  • keep data in memory using vectors
  • update the file when data changes

The storage logic is also separated from the UI logic. The CSV functions only handle reading and writing data. They do not interact with GTK widgets.