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.
Importing Modules
Section titled “Importing Modules”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 Alias Restrictions
Section titled “Import Alias Restrictions”import "std/math";
// ❌ Error: 'math' is already used as an import aliaslet math := 42;
// ❌ Error: 'math' is already used as an import aliasfn math() { }
// ❌ Error: 'math' is already used as an import aliastype 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 useslet 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 uppercaseconst PI := 3.14159;fn Add(a: i32, b: i32) -> i32 { return a + b; }type Point struct { .X: i32, .Y: i32 };
// Private - starts with lowercaseconst internal_buffer := 256;fn helper() { /* ... */ }type internal struct { .data: i32 };Struct Field Visibility
Section titled “Struct Field Visibility”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:
- Methods of that type (via receiver)
- Struct literals (to provide initial values)
- Direct field access
.fieldon 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 moduleimport "your_project/types";
let p := { .Name = "Alice", .Age = 30, .ssn = "123" } as types::Person;
let name := p.Name; // ✅ OK - public fieldlet age := p.Age; // ✅ OK - public fieldlet ssn := p.ssn; // ❌ Error - private fieldEnum Variants Inherit Visibility
Section titled “Enum Variants Inherit Visibility”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