Multi-File Projects

In this chapter, we'll explore organize code across multiple files in mana projects.

Multi-File Projects

As your project grows, you'll want to split code across multiple files. Each file declares its own module and can import from others.

Project Structure

A typical Mana project:

my_game/
├── mana.toml
├── src/
│   ├── main.mana
│   ├── game.mana
│   ├── player.mana
│   └── utils/
│       ├── math.mana
│       └── strings.mana
├── tests/
│   └── test_player.mana
└── build/

The mana.toml File

Every project needs a mana.toml configuration:

[package]
name = "my_game"
version = "1.0.0"
authors = ["Your Name"]
 
[build]
output = "build"
target = "executable"
 
[[bin]]
name = "my_game"
path = "src/main.mana"

Example Multi-File Project

src/main.mana:

module main
 
use player.Player
use game.Game
 
fn main() -> void {
    let player = Player.new("Hero", 100)
    let mut game = Game.new()
    game.add_player(player)
    game.run()
}

src/player.mana:

module player
 
pub struct Player {
    pub name: string,
    pub health: int,
    score: int,
}
 
impl Player {
    pub fn new(name: string, health: int) -> Player {
        return Player {
            name: name,
            health: health,
            score: 0,
        }
    }
 
    pub fn take_damage(mut self, amount: int) -> void {
        self.health = self.health - amount
        if self.health < 0 {
            self.health = 0
        }
    }
 
    pub fn is_alive(self) -> bool {
        return self.health > 0
    }
 
    pub fn add_score(mut self, points: int) -> void {
        self.score = self.score + points
    }
}

src/game.mana:

module game
 
use player.Player
use std.collections.Vec
 
pub struct Game {
    players: Vec<Player>,
    running: bool,
}
 
impl Game {
    pub fn new() -> Game {
        return Game {
            players: Vec::new(),
            running: false,
        }
    }
 
    pub fn add_player(mut self, player: Player) -> void {
        self.players.push(player)
    }
 
    pub fn run(mut self) -> void {
        self.running = true
        println("Game started!")
 
        while self.running {
            self.update()
        }
    }
 
    fn update(mut self) -> void {
        // Game logic
    }
}

Submodule Files

For nested modules, use directory structure:

src/utils/math.mana:

module utils.math
 
pub fn clamp(value: int, min: int, max: int) -> int {
    if value < min {
        return min
    }
    if value > max {
        return max
    }
    return value
}
 
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
    return a + (b - a) * t
}

src/utils/strings.mana:

module utils.strings
 
pub fn truncate(s: string, max_len: int) -> string {
    if s.len() <= max_len {
        return s
    }
    return s.substring(0, max_len) + "..."
}

Using submodules:

module main
 
use utils.math.clamp
use utils.strings.truncate
 
fn main() -> void {
    let value = clamp(150, 0, 100)  // 100
    let text = truncate("Hello, World!", 5)  // "Hello..."
}

Building Multi-File Projects

The Mana compiler automatically resolves imports:

# Build the project
mana build
 
# Or compile directly
mana_lang src/main.mana -c -o build/my_game

Best Practices

One Concept Per File

src/
├── main.mana        # Entry point only
├── player.mana      # Player struct and methods
├── enemy.mana       # Enemy struct and methods
├── weapon.mana      # Weapon struct and methods
└── game.mana        # Game loop and coordination

Group Related Files

src/
├── main.mana
├── entities/
│   ├── player.mana
│   ├── enemy.mana
│   └── npc.mana
├── systems/
│   ├── physics.mana
│   ├── rendering.mana
│   └── audio.mana
└── utils/
    ├── math.mana
    └── helpers.mana

Keep Modules Focused

Each module should have a single responsibility:

// Good: focused module
module collision
 
pub fn check_collision(a: Rect, b: Rect) -> bool { }
pub fn resolve_collision(a: Rect, b: Rect) -> void { }
// Avoid: unfocused module mixing concerns
module stuff
 
pub fn check_collision() { }
pub fn play_sound() { }
pub fn render_sprite() { }
pub fn save_file() { }

Summary

Mana's module system lets you:

  • Declare modules with module name at the top of each file
  • Control visibility with pub
  • Import items with use
  • Organize code across files and directories

Next, explore Common Collections in Chapter 8.