Skip to main content

State and Messages

In Iced, your application’s behavior is defined by two key components: State (what data your application holds) and Messages (what events can change that data).

State

State represents all the data your application needs to function. It can be as simple as a single value or as complex as a nested structure.

Simple State

The simplest state is a primitive value:
// State is just a u64 counter
fn main() -> iced::Result {
    iced::run(update, view)  // u64::default() is 0
}

fn update(counter: &mut u64, message: Message) {
    match message {
        Message::Increment => *counter += 1,
    }
}

fn view(counter: &u64) -> Element<'_, Message> {
    button(text(counter)).on_press(Message::Increment).into()
}
When using iced::run, the state type must implement Default. The initial state is created by calling State::default().

Custom State Structs

For real applications, you’ll want to define your own state struct:
#[derive(Default)]
struct Counter {
    value: u64,
}

fn update(counter: &mut Counter, message: Message) {
    match message {
        Message::Increment => counter.value += 1,
    }
}

fn view(counter: &Counter) -> Element<'_, Message> {
    button(text(counter.value)).on_press(Message::Increment).into()
}

Complex State

State can contain any data your application needs:
#[derive(Default)]
struct State {
    input_value: String,
    filter: Filter,
    tasks: Vec<Task>,
    dirty: bool,
    saving: bool,
}

#[derive(Debug, Clone)]
struct Task {
    id: Uuid,
    description: String,
    completed: bool,
    state: TaskState,
}
Your state can include any Rust types: primitives, strings, vectors, hash maps, custom enums, and more.

State Variants with Enums

For applications with distinct modes or screens, use enums:
#[derive(Debug)]
enum Todos {
    Loading,
    Loaded(State),
}

#[derive(Debug, Default)]
struct State {
    input_value: String,
    tasks: Vec<Task>,
}
This pattern allows you to:
  • Represent different application states explicitly
  • Ensure certain data only exists in specific states
  • Handle each state differently in your view and update logic

Messages

Messages represent all the events that can occur in your application. They’re the only way to change your state.

Defining Messages

Messages are typically defined as enums:
#[derive(Debug, Clone, Copy)]
enum Message {
    Increment,
    Decrement,
}
Messages must implement Clone because they may be produced by multiple widgets or events.

Messages with Data

Messages often carry data about the event:
#[derive(Debug, Clone)]
enum Message {
    InputChanged(String),
    CreateTask,
    FilterChanged(Filter),
    TaskMessage(usize, TaskMessage),
    TabPressed { shift: bool },
}
Message::TaskMessage(usize, TaskMessage)
//                   ^^^^^  ^^^^^^^^^^^^
//                   index  nested message
Use tuple variants when the data is positional and obvious from context.

Nested Messages

For larger applications, you can nest messages to organize them by component:
enum Message {
    Contacts(contacts::Message),
    Conversation(conversation::Message),
}

mod contacts {
    #[derive(Debug, Clone)]
    pub enum Message {
        Select(usize),
        Search(String),
    }
}

mod conversation {
    #[derive(Debug, Clone)]
    pub enum Message {
        SendMessage(String),
        LoadHistory,
    }
}

The State-Message Connection

State and Messages work together to define your application’s behavior:
1

State Holds Data

Your state struct contains all the data your UI displays:
struct Counter {
    value: u64,  // This is what we display
}
2

Messages Describe Changes

Your message enum describes what can change:
enum Message {
    Increment,  // This tells us to increase the value
}
3

Update Applies Changes

The update function connects them:
fn update(counter: &mut Counter, message: Message) {
    match message {
        Message::Increment => counter.value += 1,
    }
}

Real-World Example

Here’s a complete example from the Iced counter example (examples/counter/src/main.rs):
use iced::widget::{button, column, text};
use iced::Center;

#[derive(Default)]
struct Counter {
    value: i64,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    Increment,
    Decrement,
}

impl Counter {
    fn update(&mut self, message: Message) {
        match message {
            Message::Increment => {
                self.value += 1;
            }
            Message::Decrement => {
                self.value -= 1;
            }
        }
    }

    fn view(&self) -> Element<'_, Message> {
        column![
            button("Increment").on_press(Message::Increment),
            text(self.value).size(50),
            button("Decrement").on_press(Message::Decrement)
        ]
        .padding(20)
        .align_x(Center)
        .into()
    }
}

pub fn main() -> iced::Result {
    iced::run(Counter::update, Counter::view)
}

Best Practices

State Guidelines

  • Keep state minimal - only store what you need
  • Use #[derive(Default)] when possible for simple initialization
  • Group related data into structs
  • Use enums for states that are mutually exclusive

Message Guidelines

  • Make messages descriptive - Increment is better than ButtonPressed
  • Always derive Clone and Debug
  • Include relevant data in the message
  • Use nested messages to organize large applications

Next Steps

Build docs developers (and LLMs) love