---
title: "Hello world"
description: "One message in, one message out, on tokio, against a broker you started five minutes ago."
---

> Queen MQ documentation, for AI agents
> Complete self-contained summary of Queen MQ: https://queenmq.com/llms-brief.txt
> Fetch that first when the question is about the product rather than about this page.
> Index of all pages: https://queenmq.com/llms.txt

# Hello world

Nothing is created before it is used. The queue in the program below comes into existence with
the push that names it, inside the same transaction that stores the message, so there is no
declare step and nothing to provision.

The read takes the message under a **lease**: it is claimed until it is acknowledged or the lease
expires, and the acknowledgement is what commits consumption. No consumer group is named here, so
the read goes through the queue's own cursor, which starts at the beginning. Named groups are
[the next tutorial](/use/rust-client/multi-queue-flow): a group created after a message was pushed
starts at the tail and would find nothing.

Nothing in the program throws. Every call hands back a `Result`, so each `?` on the way down is
the check, and a failed `pop` is an `Err` rather than an empty vector: an empty claim comes back
as `Ok` with nothing in it, which is what the drain at the end tests. The acknowledgement takes
the message itself, because this client reads the consumer group and the lease id off it.

<pre class="mermaid">{`
sequenceDiagram
  participant C as client
  participant B as broker
  participant PG as PostgreSQL
  C->>B: push, naming a queue and a partition
  B->>PG: create both if absent, store the message
  PG-->>B: offset allocated
  B-->>C: 201, per-item status queued
  C->>B: pop, with a batch size and a wait
  B->>PG: claim the partition under a lease
  PG-->>B: messages from committed + 1
  B-->>C: 200, the messages and a leaseId
  C->>B: ack, by transactionId and partitionId
  B->>PG: move committed past it, release the lease
  B-->>C: 200, success true on the item
  Note over PG: nothing was deleted.<br/>The cursor moved.
`}</pre>

```rust title="examples/tutorials/rust/src/bin/01_hello_world.rs"
//
// Tutorial 1 of 5: hello world.
//
// One message in, one message out. Nothing is created in advance: the queue and
// the partition come into existence with the push that names them.
//
// Run it:
//   QUEEN_URL=http://localhost:6632 cargo run --bin 01_hello_world
//
// The program checks its own outcome and exits non-zero if a check fails.

use std::time::{SystemTime, UNIX_EPOCH};

use queen_mq::{Config, PushStatus, Queen};
use serde_json::json;

struct Checks(usize);

impl Checks {
    fn assert(&mut self, condition: bool, description: &str) -> Result<(), String> {
        if !condition {
            return Err(description.to_string());
        }
        self.0 += 1;
        println!("  ok: {description}");
        Ok(())
    }
}

// Rust has no exceptions, so the shape the other tutorials get from try/catch
// comes from `run` returning a Result: every `?` on the way down is a failed
// check or a failed call, and main turns it into FAIL and a non-zero exit.
#[tokio::main]
async fn main() {
    match run().await {
        Ok(checks) => println!("\nPASS: {checks} checks"),
        Err(e) => {
            eprintln!("\nFAIL: {e}");
            std::process::exit(1);
        }
    }
}

async fn run() -> Result<usize, String> {
    let url = std::env::var("QUEEN_URL").unwrap_or_else(|_| "http://localhost:6632".into());

    // The name is prefixed per language and suffixed per run, so every tutorial
    // in every language can share one broker and no run inherits state from
    // another.
    let run_id = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis();
    let queue = format!("tut-rust-hello-{run_id}");

    let mut checks = Checks(0);
    println!("broker {url}");

    // Signal handlers are opt-in in this client: they sit behind the `signals`
    // feature and nothing is installed unless the application asks. So SIGINT
    // and SIGTERM are left alone and this program owns its own shutdown,
    // through close() at the bottom.
    //
    // connect() validates the configuration but opens no socket; the first
    // request does that.
    let queen = Queen::connect(Config::new(&url)).map_err(|e| e.to_string())?;

    // A push names a queue and, optionally, a partition. Both are created by
    // this call if they do not exist, inside the transaction that stores the
    // message. There is no declare step and nothing to provision first.
    //
    // push() takes anything that serialises, and mints a UUIDv7 transaction id
    // client-side, which is the key the broker deduplicates on. One result per
    // item comes back.
    let pushed = queen
        .queue(&queue)
        .push(json!({ "greeting": "Hello World!" }))
        .await
        .map_err(|e| e.to_string())?;

    let stored = pushed
        .first()
        .ok_or("the broker answered the push with no result")?;
    println!("pushed {} -> {:?}", stored.transaction_id, stored.status);
    checks.assert(
        stored.status == PushStatus::Queued,
        "the broker stored the message",
    )?;

    // pop() takes messages under a lease: they are claimed until they are
    // acknowledged or the lease expires. wait(true) turns on long polling, so
    // the call parks until a message arrives instead of coming back empty. It
    // is this client's default, and it is spelled out here because the drain
    // check below turns it off.
    //
    // No consumer group is named here, so the read goes through the queue's own
    // cursor, which starts at the beginning. Named groups are tutorial 2: a
    // group created after a message was pushed starts at the tail and would see
    // nothing here.
    //
    // Unlike the JavaScript client, a failed pop is an Err rather than an empty
    // vector — turning a 403 or an exhausted retry budget into "no messages"
    // would make an outage look like an idle queue. An empty claim is still an
    // Ok with nothing in it.
    let messages = queen
        .queue(&queue)
        .batch(1)
        .wait(true)
        .pop()
        .await
        .map_err(|e| e.to_string())?;

    checks.assert(messages.len() == 1, "one message came back")?;
    let message = &messages[0];
    println!(
        "received \"{}\" from partition {}",
        message.data["greeting"].as_str().unwrap_or(""),
        message.partition
    );
    checks.assert(
        message.data["greeting"] == json!("Hello World!"),
        "the payload survived the round trip",
    )?;

    // No partition was named on the push, so the broker put the message in the
    // queue's default lane.
    checks.assert(
        message.partition == "Default",
        "it landed in the default partition",
    )?;

    // The acknowledgement is what commits consumption. It moves the cursor past
    // the message and releases the lease. This client reads the consumer group
    // and the lease id off the message rather than taking them as arguments, so
    // an ack cannot be pointed at the wrong cursor.
    //
    // A rejected ack still arrives as HTTP 200 with success: false on the item,
    // so the per-item flag is the only proof the broker took it.
    let ack = queen.ack(message).await.map_err(|e| e.to_string())?;
    checks.assert(ack.success, "the acknowledgement was accepted")?;

    // The cursor is now past the only message, so a further read finds nothing.
    // wait(false) returns immediately instead of long polling.
    let leftovers = queen
        .queue(&queue)
        .wait(false)
        .pop()
        .await
        .map_err(|e| e.to_string())?;
    checks.assert(leftovers.is_empty(), "the queue is drained")?;

    // Clean up on success only: a failed run leaves the queue on the broker to
    // be looked at.
    queen
        .queue(&queue)
        .delete()
        .await
        .map_err(|e| e.to_string())?;

    // close() flushes any client-side push buffers. This program uses none, and
    // nothing here holds the runtime open the way a Node event loop would, but
    // running it is the habit that keeps a buffered program honest.
    queen.close().await.map_err(|e| e.to_string())?;

    Ok(checks.0)
}
```

## Run it

Against a broker from [the quickstart](/start/quickstart), from `examples/tutorials/rust`:

```bash
QUEEN_URL=http://localhost:6632 cargo run --bin 01_hello_world
```

The program checks its own outcome and exits non-zero if a check fails. Every tutorial in this
section runs in the repository's own suite: `examples/tutorials/run.sh rust`.

Next: [Multi-queue flow](/use/rust-client/multi-queue-flow).

Source: https://queenmq.com/use/rust-client/hello-world/index.mdx
