---
title: Hello World in Rust
description: Create your first WendyOS application using Rust
language: rust
---

## Creating Your First Rust Application

This guide will walk you through creating your first WendyOS application in Rust. The Wendy Rust template scaffolds a small, long-running HTTP server (built with [Axum](https://docs.rs/axum/latest/axum/)) so you can prove out the full workflow: build on your machine, deploy over USB or Wi-Fi, and open the running app in your browser.

### Prerequisites

- Wendy CLI installed on your development machine
- Rust 1.85 or later installed (via `rustup`)
- A WendyOS device plugged in over USB or connectable over Wi-Fi

<Callout type="info">
  **Containerized Deployment**: Unlike Swift, which compiles natively for WendyOS, Rust applications run inside containers on your WendyOS device. The Wendy CLI handles the local build setup when deploying Rust applications.
</Callout>

### Creating the Project

<Steps>
  <Step>
    ### Initialize the Project

    Start from the Wendy Rust template:

    ```bash
    wendy init hello-world --target wendyos --language rust --template simple-api --var APP_ID=hello-world --var PORT=4001 --assistant skip --git-init no
    cd hello-world
    ```

    <CliShot flow="init" step="wizard" alt="wendy init scaffolding the Rust hello-world project" />

    The template creates `Cargo.toml`, `src/main.rs`, `Dockerfile`, and `wendy.json`. Continue below to understand the generated project.
  </Step>

  <Step>
    ### Run on WendyOS

    ```bash
    wendy run
    ```

    <CliShot flow="run" step="deploy" alt="wendy run building and deploying the Rust hello-world app" />

    <CliClip flow="run" alt="wendy run deploying the hello-world app end to end" />

    Wendy will build the app, ask you to select a device if one is not already configured, deploy the app, and show the run output. Because the app is a web server, it keeps running and your browser opens to it automatically (see the `postStart` hook below).
  </Step>
</Steps>

## Code Breakdown

<Steps>
  <Step>
    ### Explore the Project Structure

    The initialized project has this structure:

    ```
    hello-world/
    ├── Cargo.toml
    ├── Dockerfile
    ├── wendy.json
    └── src/
        └── main.rs
    ```

    The main application code is in `src/main.rs`.
  </Step>

  <Step>
    ### Verify the Code

    The generated `src/main.rs` starts an Axum HTTP server. The root route (`/`) returns a JSON message, and there are also `/health` and `POST /items` routes to demonstrate JSON handling:

    ```rust
    use axum::{
        http::StatusCode,
        routing::{get, post},
        Json, Router,
    };
    use serde::{Deserialize, Serialize};
    use std::env;

    #[tokio::main]
    async fn main() {
        let hostname = env::var("WENDY_HOSTNAME").unwrap_or_else(|_| "0.0.0.0".to_string());

        let app = Router::new()
            .route("/", get(root))
            .route("/health", get(health))
            .route("/items", post(create_item));

        let listener = tokio::net::TcpListener::bind("0.0.0.0:4001").await.unwrap();
        println!("Server running on http://{}:4001", hostname);
        axum::serve(listener, app).await.unwrap();
    }

    async fn root() -> Json<serde_json::Value> {
        println!("Received request: GET /");
        Json(serde_json::json!({"message": "hello-world"}))
    }

    async fn health() -> Json<serde_json::Value> {
        Json(serde_json::json!({"status": "ok"}))
    }

    async fn create_item(Json(payload): Json<CreateItem>) -> (StatusCode, Json<ItemResponse>) {
        println!("Received request: POST /items - {}", payload.name);
        let item = ItemResponse {
            id: 1,
            name: payload.name,
            price: payload.price,
        };
        (StatusCode::CREATED, Json(item))
    }

    #[derive(Deserialize)]
    struct CreateItem {
        name: String,
        price: f64,
    }

    #[derive(Serialize)]
    struct ItemResponse {
        id: u64,
        name: String,
        price: f64,
    }
    ```

    <Callout type="info">
      **It's a server, not a one-shot script**: This app binds to `0.0.0.0:4001` and serves requests until it's stopped, so it stays in the `Running` state on your device. Requesting `/` returns `{"message":"hello-world"}`.
    </Callout>
  </Step>

  <Step>
    ### Verify Cargo.toml

    Your `Cargo.toml` declares the Axum, Tokio, and Serde dependencies:

    ```toml
    [package]
    name = "hello-world"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    axum = "0.8.8"
    tokio = { version = "1", features = ["full"] }
    serde = { version = "1", features = ["derive"] }
    serde_json = "1"
    ```
  </Step>

  <Step>
    ### Generated Dockerfile

    The generated project includes a multi-stage Dockerfile that builds the release binary with `rust:1.85-slim` and runs it on `debian:bookworm-slim`:

    ```dockerfile
    FROM rust:1.85-slim AS builder

    WORKDIR /app
    COPY Cargo.toml ./
    COPY src ./src

    RUN cargo build --release

    FROM debian:bookworm-slim

    RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates \
        && rm -rf /var/lib/apt/lists/*

    COPY --from=builder /app/target/release/hello-world /usr/local/bin/hello-world

    EXPOSE 4001

    CMD ["hello-world"]
    ```
  </Step>

  <Step>
    ### Generated wendy.json

    The generated `wendy.json` already wires up a readiness probe and a `postStart` hook, so the CLI waits for port `4001` and then opens your browser to the running app — you don't add these by hand:

    ```json
    {
        "appId": "hello-world",
        "platform": "linux",
        "version": "0.1.0",
        "entitlements": [
            {
                "type": "network"
            }
        ],
        "readiness": {
            "tcpSocket": { "port": 4001 },
            "timeoutSeconds": 30
        },
        "hooks": {
            "postStart": {
                "cli": "wendy utils open-browser http://${WENDY_HOSTNAME}:4001"
            }
        }
    }
    ```
  </Step>
</Steps>

## Run Again on WendyOS

Deploy your containerized application to your WendyOS device:

```bash
wendy run
```

The Wendy CLI will build your Docker image, transfer it to your device, and run it. Once port `4001` is accepting connections, your browser opens automatically to `http://<your-device-hostname>:4001`, where you'll see:

```json
{"message":"hello-world"}
```

## Verifying Deployment on Your Device

After deploying your application to a WendyOS device, you can verify it is running by listing the applications on your device:

```bash
wendy device apps list
✔︎ Searching for WendyOS devices [5.3s]
✔︎ Listing applications: True Probe [USB, Ethernet, LAN]
╭───────────────┬─────────┬─────────┬──────────╮
│ App           │ Version │ State   │ Failures │
├───────────────┼─────────┼─────────┼──────────┤
│ hello-world   │ 0.1.0   │ Running │ 0        │
╰───────────────┴─────────┴─────────┴──────────╯
```

<CliShot flow="apps-list" step="apps" alt="wendy device apps list showing the running hello-world app" />

<Callout type="info">
  **Expected State**: The app shows as "Running" because it's a long-lived HTTP server — it keeps listening for requests until you stop it. The "Failures" column showing `0` confirms it started cleanly without crashing.
</Callout>

## Next Steps

Now that you have a basic Rust application running:

- Learn how to build a [Simple Web Server](../../../../guides/tutorials/rust/simple-web-server/) with Axum
- Explore environment variables and configuration
- Build more complex applications with networking and data processing
- Integrate with WendyOS device features via HTTP APIs

<Callout type="info">
  **Containerized Deployment**: Rust applications on WendyOS run in containers. This provides isolation and portability while the Wendy CLI manages the deployment workflow.
</Callout>

<Accordions>
  <Accordion title="Testing the app on your own machine">
    There are times when you might not have a WendyOS device around. You can still run the server locally using Cargo or Docker.

    ### Using Cargo Directly

    Run your application locally using Cargo:

    ```bash
    cargo run
    ```

    You should see output similar to:

    ```
       Compiling hello-world v0.1.0 (/path/to/hello-world)
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.21s
         Running `target/debug/hello-world`
    Server running on http://0.0.0.0:4001
    ```

    The server keeps running. In another terminal, hit the root route:

    ```bash
    curl http://localhost:4001
    ```

    You should see:

    ```json
    {"message":"hello-world"}
    ```

    ### Using Docker Locally

    Build and run your application in a Docker container:

    ```bash
    docker build -t hello-world .
    docker run -p 4001:4001 hello-world
    ```

    Then, in another terminal:

    ```bash
    curl http://localhost:4001
    ```

    You should see:

    ```json
    {"message":"hello-world"}
    ```
  </Accordion>
</Accordions>
