Skip to content

Modules

Modules in ferret are nothing but files. Each file is a module, and the module name is derived from the file path from the root of the project.

For example, if you have a file structure like this:

  • Directoryyour_project
    • main.fer
    • Directoryutils
      • math.fer

The module name for math.fer would be your_project/utils/math.

To use a module in another file, you use the import statement followed by the module path.

import "your_project/utils/math";

To use access functions or variables from the imported module, you use the module name as a prefix followed by :: and then the function or variable name.

let result := math::Some_function();

You can also use as to create an alias for the module to shorten the name or avoid naming conflicts.

import "your_project/utils/math" as m;
let result := m::Some_function();
import "std/math";
// ❌ Error: 'math' is already used as an import alias
let math := 42;
// ❌ Error: 'math' is already used as an import alias
fn math() { }
// ❌ Error: 'math' is already used as an import alias
type math struct { .x: i32; }

This restriction prevents naming conflicts and keeps your code clear. If you need to use the name, provide a different import alias:

import "std/math" as m; // Use 'm' as the alias instead
// ✅ Now 'math' is available for other uses
let math := 42;

Exporting from Modules (Capitalization-Based)

Section titled “Exporting from Modules (Capitalization-Based)”

Ferret uses capitalization to control symbol visibility, similar to Go:

  • Uppercase names are exported (public) - accessible from other modules
  • Lowercase names are private - only accessible within the defining module
// In your_project/utils/math.fer
// Exported (public) - starts with uppercase
const PI := 3.14159;
fn Add(a: i32, b: i32) -> i32 { return a + b; }
type Point struct { .X: i32, .Y: i32 };
// Private - starts with lowercase
const internal_buffer := 256;
fn helper() { /* ... */ }
type internal struct { .data: i32 };

Field names in structs follow the capitalization rule:

type Person struct {
.Name: str, // Public field (uppercase)
.Age: i32, // Public field (uppercase)
.ssn: str // Private field (lowercase)
};

Private field access rules:

  • Public fields (uppercase) → accessible everywhere
  • Private fields (lowercase) → only accessible in:
    1. Methods of that type (via receiver)
    2. Struct literals (to provide initial values)
  • Direct field access .field on private fields is not allowed outside methods
type Account struct {
.Owner: str, // Public
.balance: i32 // Private
};
fn (a: Account) GetBalance() -> i32 {
return a.balance; // ✅ OK - method can access private field
}
fn (a: &Account) Deposit(amount: i32) {
a.balance = a.balance + amount; // ✅ OK - method can modify
}
fn test() {
// ✅ OK - struct literal provides all fields
let acc := { .Owner = "Alice", .balance = 1000 } as Account;
let owner := acc.Owner; // ✅ OK - public field
let bal := acc.balance; // ❌ Error - private field
let bal2 := acc.GetBalance(); // ✅ OK - use method instead
}

Cross-module field access:

// In another module
import "your_project/types";
let p := { .Name = "Alice", .Age = 30, .ssn = "123" } as types::Person;
let name := p.Name; // ✅ OK - public field
let age := p.Age; // ✅ OK - public field
let ssn := p.ssn; // ❌ Error - private field

Enum variants automatically inherit the visibility of their parent enum:

type Status enum { Pending, Active, Closed }; // Exported enum
// Status::Pending, Status::Active, Status::Closed are all exported
type internal enum { A, B, C }; // Private enum
// internal::A, internal::B, internal::C are all private