Skip to content

Modules & Imports

Modules in Ferret organize code into reusable units. Understanding how modules work is essential for building larger applications.

Each .fer file is a module. The module is identified by its import path, which can be:

  1. Standard library - Built-in modules (std/io, net/http, db/sql)
  2. Local project files - Files in your project
  3. External packages - Dependencies from fer.ret
  4. Neighbor packages - Local packages in your workspace
import "std/io";
import "net/http";
import "db/sql";

No manifest required for stdlib imports.

For files within your project:

  • Directorymyproject
    • fer.ret
    • main.fer
    • Directoryutils
      • math.fer
      • string.fer
# fer.ret
[package]
name = "myproject"
// In main.fer
import "myproject/utils/math";
import "myproject/utils/string";
fn main() {
let result := math::Add(5, 3);
}

For dependencies declared in fer.ret:

# fer.ret
[dependencies]
logger = "github.com/user/logger@^v1.0.0"
import "github.com/user/logger";
fn main() {
logger::Info("Hello!");
}

For local workspace packages:

  • Directoryworkspace
    • Directoryshared
      • fer.ret (name = “shared”)
      • utils.fer
    • Directorymyapp
      • fer.ret
      • main.fer
myapp/fer.ret
[dependencies]
shared = "../shared"
// In myapp/main.fer
import "shared/utils";
fn main() {
utils::Helper();
}
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
// 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)
};

Access rules:

  • Public fields (uppercase) → accessible everywhere
  • Private fields (lowercase) → only in methods and struct literals
type Account struct {
.Owner: str, // Public
.balance: i32 // Private
};
fn (a: Account) GetBalance() -> i32 {
return a.balance; // ✅ OK - method can access
}
fn test() {
let acc := { .Owner = "Alice", .balance = 1000 } as Account;
let owner := acc.Owner; // ✅ OK - public
let bal := acc.balance; // ❌ Error - private
let bal2 := acc.GetBalance(); // ✅ OK - use method
}

Import with an alias to shorten names:

import "std/math" as m;
import "github.com/user/logger" as log;
fn main() {
let result := m::Sqrt(16.0);
log::Info("Result calculated");
}

Default aliases use the last path segment:

  • import "std/math";math
  • import "github.com/user/logger";logger

Use :: to access exported members:

import "std/io";
import "myproject/utils/math";
fn main() {
io::Println("Hello!");
let x := math::PI;
let p := math::Point{ .X: 5 };
}

Ferret resolves imports in this order:

  1. Cached modules - Already loaded
  2. Standard library - std/, net/, db/
  3. Local project - Files via package name
  4. Dependencies - From fer.ret
  5. Error - Not found
  • ✅ Clear, descriptive module names
  • ✅ Single responsibility per module
  • ✅ Export only necessary symbols
  • ✅ Use aliases for long paths
  • ✅ Declare dependencies in fer.ret
  • ❌ Avoid circular dependencies
  • ❌ Don’t reuse import alias names
  • Directorymyapp
    • fer.ret
    • main.fer
    • Directorymodels
      • user.fer
    • Directoryservices
      • auth.fer
# fer.ret
[package]
name = "myapp"
[dependencies]
logger = "github.com/user/logger@^v1.0.0"
main.fer
import "std/io";
import "github.com/user/logger";
import "myapp/models/user";
import "myapp/services/auth";
fn main() {
logger::Info("Starting...");
let u := user::New("Alice");
auth::Login(u);
io::Println("Welcome!");
}
models/user.fer
type User struct {
.Name: string,
.Id: i32,
}
fn New(name: string) -> User {
return User{ .Name: name, .Id: 0 };
}
// Private helper
fn validate(name: string) -> bool {
return name != "";
}