Project Structure

Part of: Reef Language Reference Last reviewed on version: 0.8.0


Overview

This guide covers how to organize Reef projects, use the build system, and configure your project with reef.toml.


Creating a New Project

Using reefc new

reefc new myproject

This creates:

myproject/
├── reef.toml           # Project manifest
├── README.md           # Project readme
├── .gitignore          # Git ignore file
├── src/
│   └── main.reef       # Entry point
├── tests/              # Test files
└── docs/               # Documentation output

Project Naming Rules

Project names must:

  • Start with a lowercase letter
  • Contain only: a-z, 0-9, _, -
# Valid names
reefc new myproject
reefc new my-app
reefc new web_server_2

# Invalid names
reefc new MyProject     # No uppercase
reefc new 123app        # Must start with letter
reefc new my.project    # No dots

Initialize Existing Directory

To add Reef to an existing project:

cd existing-project
reefc init

This creates reef.toml without overwriting existing files.


Project Layout

myproject/
├── reef.toml           # Required: Project configuration
├── src/
│   ├── main.reef       # Entry point (proc main)
│   ├── utils.reef      # Utility module
│   └── models/
│       ├── user.reef   # Submodule
│       └── order.reef  # Submodule
├── tests/
│   ├── test_utils.reef
│   └── test_models.reef
├── docs/               # Generated documentation
├── build/              # Build artifacts (generated)
└── README.md

Entry Point

The compiler looks for the entry point in this order:

  1. src/main.reef
  2. main.reef

The entry point must contain a main function, which can be either a procedure or a function returning an exit code:

// Option 1: proc main() - always exits with code 0
proc main()
    println("Hello, world!")
end main

// Option 2: fn main(): int - return exit code to OS
fn main(): int
    if some_error
        return 1  // Exit with error
    end if
    return 0      // Exit with success
end main

Exit Codes:

  • proc main() always returns exit code 0 to the operating system
  • fn main(): int returns whatever integer value you return
  • Use exit(code) to exit immediately from anywhere in the program

reef.toml Configuration

Full Example

[package]
name = "myproject"
version = "0.1.0"
author = "Your Name <email@example.com>"
description = "A Reef project"
license = "MIT"
url = "https://github.com/yourusername/myproject"

[build]
entry = "src/main.reef"
output = "myproject"
output_dir = "build"
source_dirs = ["src"]

[docs]
output = "docs"
include_private = false

[package] Section

Field Required Description
name Yes Project name (lowercase, no spaces)
version Yes Semantic version (e.g., "1.0.0")
author No Author name and email
description No Short project description
license No License identifier (e.g., "MIT", "Apache-2.0")
url No Project homepage or repository URL

[build] Section

Field Default Description
entry "src/main.reef" Entry point file containing proc main()
output project name Name of the compiled executable
output_dir "build" Directory for build artifacts
source_dirs ["src"] Additional directories to search for local modules. Each entry is resolved relative to the project root (where reef.toml lives), searched non-recursively, after the entry file's own directory and src/.

[docs] Section

Field Default Description
output "docs" Directory for generated documentation
include_private false Include non-exported items in docs

Build Commands

reefc build

Compile the project:

cd myproject
reefc build

This:

  1. Reads reef.toml
  2. Compiles src/main.reef (or main.reef)
  3. Resolves and compiles imported modules
  4. Links with the runtime
  5. Creates executable in build/ (named after project)

reefc run

Build and execute:

reefc run

Equivalent to reefc build && ./build/myproject

reefc run (single file)

Run a specific file without a project:

reefc run examples/hello.reef

Common Options

reefc build --check      # Type-check only, don't compile
reefc build --emit-c     # Generate C code only
reefc build --keep-c     # Keep intermediate C file
reefc build -v           # Verbose output

Module Organization

File-to-Module Mapping

Each .reef file defines a module. The module name matches the file path:

File Path Module Name
src/main.reef (entry point, no module)
src/utils.reef utils
src/models/user.reef models.user
lib/http/client.reef http.client

Declaring Modules

// src/utils.reef
module utils

export
    fn helper(): int
end export

fn helper(): int
    return 42
end helper

end module

Importing Modules

// src/main.reef
import utils
import models.user
import http.client as http

proc main()
    let x = helper()           // From utils
    let u = user.create()      // From models.user
    let c = http.connect()     // From http.client (aliased)
end main

Import Aliases

import very.long.module.name as short

// Use short.function() instead of very.long.module.name.function()

Module Resolution

The compiler searches for modules in this order:

  1. Current directory

    • import foo looks for ./foo.reef
  2. Project source directory (src/)

    • import foo looks for src/foo.reef
    • import models.user looks for src/models/user.reef
  3. Standard library (via REEF_STDLIB_PATH or system installation)

    • import core.str looks for $REEF_STDLIB_PATH/core/str.reef
    • import net.http looks for $REEF_STDLIB_PATH/net/http.reef

Local Module Example

myproject/
├── src/
│   ├── main.reef       # import utils
│   ├── utils.reef      # Found as src/utils.reef
│   └── db/
│       └── conn.reef   # import db.conn → src/db/conn.reef

Environment Variables

Variable Purpose
REEF_STDLIB_PATH Path to standard library
REEF_RUNTIME_PATH Path to runtime library
REEF_HOME Base path (sets both STDLIB and RUNTIME paths)

Set these for development:

export REEF_HOME=/path/to/reef
# Or set individually:
export REEF_STDLIB_PATH=/path/to/reef-stdlib
export REEF_RUNTIME_PATH=/path/to/reef-runtime

Visibility and Exports

Public vs Private

By default, all declarations are private. Use export to make them public:

module mymodule

export
    fn public_function(): int
    type PublicType
    proc public_procedure()
end export

// Public - listed in export
fn public_function(): int
    return helper()  // Can call private functions
end public_function

// Private - not in export
fn helper(): int
    return 42
end helper

end module

What Can Be Exported

  • Functions (fn)
  • Procedures (proc)
  • Types (type)
  • Active Objects (active object)

Documentation Generation

Generate Documentation

reefc doc

This:

  1. Reads source files
  2. Extracts comments before declarations
  3. Generates Markdown in docs/ directory

Documentation Comments

Use regular // comments before declarations:

// Calculates the factorial of n.
// Returns 1 for n <= 1.
fn factorial(n: int): int
    if n <= 1
        return 1
    end if
    return n * factorial(n - 1)
end factorial

reefc doc Options

reefc doc                  # Generate to docs/
reefc doc --output api     # Generate to api/
reefc doc --private        # Include private items
reefc doc -v               # Verbose output

Testing

Test File Organization

myproject/
├── src/
│   └── math.reef
└── tests/
    └── test_math.reef

Test File Structure

// tests/test_math.reef
import test.framework
import math

proc main()
    let runner = new framework.TestRunner()

    runner.assert_eq_int(add(2, 3), 5, "add works")
    runner.assert_eq_int(multiply(4, 5), 20, "multiply works")

    runner.report()
end main

See 080_TESTING.md for the full test.framework API — TestRunner is qualified through the module's last path segment (framework.TestRunner()), same rule as any other dotted import.

Running Tests

# Run a specific test file
reefc run tests/test_math.reef

# Run all tests (shell script)
for test in tests/*.reef; do
    echo "Running $test..."
    reefc run "$test"
done

Build Artifacts

Generated Files

After reefc build:

myproject/
├── build/
│   └── myproject       # Executable (Linux/macOS)
│   └── myproject.exe   # Executable (Windows)
└── ...

Cleaning

reefc clean

Removes:

  • build/ directory
  • Generated .c files (if --keep-c was used)
  • Generated .o files

.gitignore

The generated .gitignore includes:

build/
*.c
*.o
*.so
*.exe
.DS_Store

Multi-File Projects

Example: Web Server

webserver/
├── reef.toml
├── src/
│   ├── main.reef           # Entry point
│   ├── config.reef         # Configuration handling
│   ├── http/
│   │   ├── server.reef     # HTTP server
│   │   ├── request.reef    # Request parsing
│   │   └── response.reef   # Response building
│   ├── handlers/
│   │   ├── static.reef     # Static file handler
│   │   └── api.reef        # API handlers
│   └── utils/
│       ├── logging.reef    # Logging utilities
│       └── json.reef       # JSON parsing
└── tests/
    ├── test_http.reef
    └── test_handlers.reef

main.reef for Multi-File Project

// src/main.reef
import config
import http.server
import handlers.static
import handlers.api
import utils.logging

proc main()
    let cfg = config.load("config.toml")
    let logger = logging.create(cfg.log_level)

    let server = new server.Server(cfg.port)
    server.add_handler("/static", static.handler)
    server.add_handler("/api", api.handler)

    logger.info("Starting server on port ${cfg.port}")
    server.start()
end main

Note: A dotted import http.server is referenced by its last path segment (server.Type, server.function()), not the full dotted path — new http.server.Server(...) is a syntax error. This matches import aliasing: import http.server as hs would instead make it hs.Server.


Best Practices

1. Keep main.reef Small

// Good: main.reef is just entry point
import app

proc main()
    app.start()
end main

Note: Name the module's entry function something other than runrun is reserved (it's the Active Object run() method keyword) and cannot be used as a proc/fn name, even module-qualified.

2. One Concept Per File

src/
├── user.reef       # User type and operations
├── order.reef      # Order type and operations
├── payment.reef    # Payment processing
src/
├── models/         # Data types
├── services/       # Business logic
├── handlers/       # Request handlers
└── utils/          # Utilities

4. Test Mirror Source Structure

src/models/user.reef    →  tests/test_user.reef
src/services/auth.reef  →  tests/test_auth.reef

5. Document Public APIs

import core.option

// User represents a registered user in the system.
// All users have a unique ID and email address.
type User = struct
    id: int
    email: string
    name: string
end User

// Creates a new user with the given email and name.
// Returns None if the email is already registered.
fn create_user(email: string, name: string): option.Option[User]
    // ... (check for an existing user, assign an id, etc.)
    return @option.Option[User].None()
end create_user

Troubleshooting

"Module not found"

  1. Check file exists at expected path
  2. Check REEF_STDLIB_PATH is set for stdlib imports
  3. Check module declaration matches filename

"Entry point not found"

  1. Ensure src/main.reef or main.reef exists
  2. Ensure it contains proc main()

"reef.toml not found"

  1. Run reefc init to create it
  2. Or create manually with [package] section

Previous: 005_COMPILER_USAGE.md Next: 015_BASICS.md