---
title: Program Structure
description: "Understand the minimal shape of a Thru Rust program, the #[entry]
  macro, and how a program exits."
source_url:
  html: https://thru.org/docs/sdks/rust-program-sdk/program-structure/
  md: https://thru.org/docs/sdks/rust-program-sdk/program-structure.md
---

# Program Structure

Use this page when you need the smallest reliable mental model for how a Rust program starts, reads input, and exits.

## Minimal program shape

```rust
#![no_std]
#![no_main]

use thru_sdk_macros::*;

#[entry(stack_size = 4096)]
fn main(_instr_data: &[u8]) -> Result<u64, u64> {
    Ok(0)
}
```

Every program is `#![no_std]` and `#![no_main]`. `thru-core` installs the `#[panic_handler]`, so do not define your own.

## The `#[entry]` macro

`#[entry]` comes from `thru-sdk-macros`. It generates the `_start` boot shim that the VM jumps to, sets up the stack, and exports the `start` symbol that calls your function.

```rust
#[entry(stack_size = 8192)]
```

| Argument | Rules |
| - | - |
| `stack_size` | Stack size in bytes. Must be a multiple of 4096. Defaults to 4096 when omitted, but the argument list must not be empty. |

## Accepted entry signatures

The macro validates your function signature and fails to compile otherwise:

```rust
fn main(instr_data: &[u8]) -> Result<u64, u64>
fn main(instr_data: &[u8], mgr: AccountManager<N>) -> Result<u64, u64>
fn main(instr_data: &[u8]) -> !
fn main(instr_data: &[u8], mgr: AccountManager<N>) -> !
```

When the second parameter is present, the macro builds the [`AccountManager`](https://thru.org/docs/sdks/rust-program-sdk/accounts-and-transaction-context.md) from the current transaction for you and exits with code `1` if it cannot be constructed.

## Execution model

| Step | What happens |
| - | - |
| Enter | The VM jumps to the generated `_start`, which validates the transaction version, sizes the stack segment, and calls `start`. |
| Read inputs | `start` turns the instruction-data pointer and length into a `&[u8]` (empty when the pointer is null) and optionally constructs the `AccountManager`. |
| Run | Your entry function runs with that slice. |
| Exit | A returned `Ok(code)` exits with `revert = 0`; `Err(code)` exits with `revert = 1`. A `!`-returning entry must exit itself. |

## Exiting explicitly

```rust
use thru_core::program_utils;

program_utils::succeed(0);      /* exit, no revert */
program_utils::revert(7);       /* exit with revert */
```

Both wrap the `exit` syscall and never return. See [Error Handling and Return Codes](https://thru.org/docs/sdks/rust-program-sdk/error-handling-and-return-codes.md).

## Multi-instruction dispatch

Programs with several instructions usually tag-dispatch on the first byte of the instruction data:

```rust
#![no_std]
#![no_main]

use thru_core::*;
use thru_sdk_macros::*;

#[entry(stack_size = 8192)]
fn main(instr_data: &[u8], mgr: AccountManager<4>) -> Result<u64, u64> {
    let Some((tag, rest)) = instr_data.split_first() else {
        return Err(1);
    };

    match tag {
        0 => initialize(rest, &mgr),
        1 => transfer(rest, &mgr),
        _ => Err(2),
    }
}
```

## Logging

```rust
use thru_core::tvm_println;

tvm_println!("balance is {}", balance);
tvm_println!(bufsize = 128, "short message {}", idx);
```

`tvm_println!` formats into a stack-allocated `heapless::String` (1024 bytes by default) and passes it to the `log` syscall.

## Related pages

- [Accounts and Transaction Context](https://thru.org/docs/sdks/rust-program-sdk/accounts-and-transaction-context.md)
- [Build Integration](https://thru.org/docs/sdks/rust-program-sdk/build-integration.md)
- [Runtime overview](https://thru.org/docs/spec/runtime/overview.md)
