Beginner

Hello World in Hare

Your first Hare program - the classic Hello World example with Docker setup

Every programming journey starts with Hello World. Let’s write our first Hare program.

The Code

Create a file named hello.ha:

1
2
3
4
5
use fmt;

export fn main() void = {
	fmt::println("Hello, World!")!;
};

Understanding the Code

  • use fmt; - Imports the fmt module from Hare’s standard library for formatted I/O
  • export fn main() void = - Declares the program entry point; export makes it visible to the runtime, void means it returns nothing
  • { ... }; - The function body is an expression terminated with a semicolon
  • fmt::println("Hello, World!")! - Prints the string followed by a newline
  • ! - The error assertion operator; it tells the compiler this I/O operation should not fail. If it does (e.g., stdout is closed), the program aborts with a trace

Running with Docker

The easiest way to run this without installing Hare locally is using Alpine Linux’s edge repository, which packages Hare:

1
2
3
4
5
# Pull the Alpine edge image
docker pull alpine:edge

# Run the program (installs Hare and compiles)
docker run --rm -v $(pwd):/code alpine:edge sh -c "apk add --no-cache hare > /dev/null 2>&1 && cd /code && hare run hello.ha"

Note: Since there is no dedicated Hare Docker image on Docker Hub, we use Alpine Linux edge which includes Hare in its package repository. The apk add step installs the compiler before running your program.

Running Locally

Hare officially supports Linux, FreeBSD, OpenBSD, NetBSD, and DragonFlyBSD. If you have Hare installed:

1
2
3
4
5
6
# Run directly (compiles and executes in one step)
hare run hello.ha

# Or build a binary first, then run it
hare build -o hello hello.ha
./hello

Expected Output

Hello, World!

Key Concepts

  1. Module imports - The use keyword imports standard library modules
  2. Entry point - export fn main() void is the program’s starting point
  3. Error handling - The ! operator is required when calling functions that may fail; it asserts success
  4. Expression-based - Function bodies are expressions (note the = after the return type and ; after the closing brace)
  5. QBE backend - Hare compiles through the QBE compiler backend, not LLVM

Understanding Error Handling

In Hare, I/O operations like fmt::println can potentially fail (e.g., if stdout is unavailable). The return type includes an error union, and Hare requires you to handle that possibility. The ! operator is one way to do it — it asserts the operation succeeds and aborts the program if it doesn’t:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use fmt;

export fn main() void = {
	// The '!' asserts success - program aborts on error
	fmt::println("This will succeed")!;

	// You could also use match to handle errors explicitly
	// match (fmt::println("Hello")) {
	// case void => void;
	// case let err: io::error => // handle error
	// };
};

This is fundamental to Hare’s philosophy: every possible error must be acknowledged by the programmer.

The export Keyword

The export keyword on main is required because the Hare runtime needs to call your main function. Without export, the function would only be visible within its own module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use fmt;

// Helper function - not exported, only visible in this module
fn greet() void = {
	fmt::println("Hello, World!")!;
};

// Entry point - must be exported for the runtime
export fn main() void = {
	greet();
};

File Extension

Hare source files use the .ha extension. The hare build driver handles compilation, linking, and execution through simple commands like hare run and hare build.

Next Steps

Continue to Variables and Data Types to explore Hare’s type system, including tagged unions, slices, and non-null pointers.

Running Today

All examples can be run using Docker:

docker pull alpine:edge
Last updated: