Building a Native GUI Application with Rust and Iced.rs
Iced.rs vs. Yew: What’s the Difference?
Rust has been gaining popularity as a programming language, and its ecosystem is rapidly expanding. One area that has seen significant growth is the development of graphical user interfaces (GUIs). Before we dive into building our application, let’s compare Iced.rs with Yew, another popular Rust framework for building web applications.
While Yew is designed specifically for web development, Iced.rs focuses on cross-platform GUI applications, making it an excellent choice for building native desktop applications. Iced.rs is also inspired by the Elm architecture, which provides a unique approach to building GUI applications.
Setting Up Iced.rs
To get started, you’ll need a recent version of Rust installed on your system. Create a new Rust project and add the necessary dependencies to your Cargo.toml
file:
[dependencies]
iced = "0.3"
iced_web = "0.3"
reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2.79"
Creating the User Interface
Our application will consist of a simple list-detail interface, where we’ll fetch data from JSONPlaceholder and display it in a list. We’ll create a data.rs
file to handle data access and define our Post
and Comment
structs:
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
}
pub struct Comment {
pub id: i32,
pub post_id: i32,
pub name: String,
pub email: String,
pub body: String,
}
We’ll also implement functions to fetch data from JSONPlaceholder using the Reqwest HTTP client:
async fn fetch_posts() -> Vec<Post> {
// Fetch posts from JSONPlaceholder
}
async fn fetch_comments(post_id: i32) -> Vec<Comment> {
// Fetch comments for a post from JSONPlaceholder
}
Building the UI with Iced.rs
Iced.rs applications consist of four central concepts: State, Messages, Update, and View. We’ll define our App
struct and implement the Application
trait, which will handle our application’s state and messaging:
struct App {
posts: Vec<Post>,
comments: Vec<Comment>,
}
impl Application for App {
type Message = Message;
type Flags = ();
fn new(_flags: ()) -> (App, Command<Message>) {
// Initialize app state and return initial command
}
fn title(&self) -> String {
// Return app title
}
fn update(&mut self, message: Message, _clipboard: &muto Clipboard) -> Command<Message> {
// Handle messages and update app state
}
fn view(&mut self, _bounds: Rectangle) -> Element<'_, Message> {
// Return app UI
}
}
We’ll also create Post
and Comment
widgets, which will render our data:
struct PostWidget {
post: Post,
}
impl Widget<Message> for PostWidget {
fn view(&mut self, _bounds: Rectangle) -> Element<'_, Message> {
// Return post widget UI
}
}
struct CommentWidget {
comment: Comment,
}
impl Widget<Message> for CommentWidget {
fn view(&mut self, _bounds: Rectangle) -> Element<'_, Message> {
// Return comment widget UI
}
}
Putting it All Together
In our main.rs
file, we’ll define our App
struct and implement the Application
trait. We’ll also define our Message
struct, which will handle our application’s data flow:
enum Message {
FetchPosts,
FetchComments(i32),
PostsFetched(Vec<Post>),
CommentsFetched(Vec<Comment>),
}
fn main() {
App::run(App::new(()).0).expect("Failed to run app");
}
Testing Our Application
Finally, we’ll run our application using trunk serve
, which will build and run our app on http://localhost:8080. We’ll see our list-detail interface in action, with posts and comments fetched from JSONPlaceholder.