Skip to content
FutureBlitz

    Install

    Modules and packages, 1 of 6

    Several files

    Split a program into files, mark what other files use as Public, and import files by their paths.

    What you’ll build

    In this tutorial you take a program that’s in one file and split it into three: one for its shapes, one for the text it prints about them, and the main file. Each file is a module, and a file sees another’s declarations only when it imports them.

    One file

    Here is the whole program in main.fb:

    Import std.core
    
    Struct Rect
        Field Width: Float
        Field Height: Float
    
        Method Area(): Float => Width * Height
    End Struct
    
    Function Square(side: Float): Rect => Rect(side, side)
    
    Function Describe(r: Rect): String => "{r.Width} by {r.Height}, area {r.Area()}"
    
    Print(Describe(Rect(4, 3)))
    Print(Describe(Square(2)))
    Output
    4 by 3, area 12
    2 by 2, area 4

    A file of its own

    Move Rect and Square into shapes.fb, beside main.fb. Everything a file declares is private to it unless it’s marked Public, so Rect, its fields, its Method, and Square all become Public. Then main.fb imports the file by its path, Import "./shapes", without the .fb.

    shapes.fb
    Import std.core
    
    Public Struct Rect
        Public Field Width: Float
        Public Field Height: Float
    
        Public Method Area(): Float => Width * Height
    End Struct
    
    Public Function Square(side: Float): Rect => Rect(side, side)
    main.fb
    Import std.core
    Import "./shapes"
    
    Function Describe(r: Rect): String => "{r.Width} by {r.Height}, area {r.Area()}"
    
    Print(Describe(Rect(4, 3)))
    Print(Describe(Square(2)))
    Output
    4 by 3, area 12
    2 by 2, area 4

    Leave Public off Square, and main.fb can’t call it:

    shapes.fb
    Import std.core
    
    Public Struct Rect
        Public Field Width: Float
        Public Field Height: Float
    
        Public Method Area(): Float => Width * Height
    End Struct
    
    Function Square(side: Float): Rect => Rect(side, side)
    main.fb
    Import std.core
    Import "./shapes"
    
    Print(Square(2).Area())
    $ fb run main.fb
       Compiling main.fb
    main.fb:4:7: error: 'Square' is private to shapes.fb
       |
     4 | Print(Square(2).Area())
       |       ^^^^^^
    shapes.fb:10:10: note: declared here
       |
    10 | Function Square(side: Float): Rect => Rect(side, side)
       |          ------
    error: couldn't build main.fb because of 1 error

    fb run main.fb builds both files into one executable. Only the main file has top-level code, the statements outside any declaration, which run when the program starts. The other files only declare things, and a statement at the top level of shapes.fb is an error.

    A folder

    A quoted path can go into folders, with / between them, and back out with ... Move Describe into text/report.fb. It needs Rect, so it imports shapes.fb from the folder above its own.

    shapes.fb
    Import std.core
    
    Public Struct Rect
        Public Field Width: Float
        Public Field Height: Float
    
        Public Method Area(): Float => Width * Height
    End Struct
    
    Public Function Square(side: Float): Rect => Rect(side, side)
    text/report.fb
    Import std.core
    Import "../shapes"
    
    Public Function Describe(r: Rect): String => "{r.Width} by {r.Height}, area {r.Area()}"
    main.fb
    Import std.core
    Import "./shapes"
    Import "./text/report"
    
    Print(Describe(Rect(4, 3)))
    Print(Describe(Square(2)))
    Output
    4 by 3, area 12
    2 by 2, area 4

    A path is always from the importing file’s folder. It can’t leave the folder the main file is in, so a program’s files all live under it. In a package, which project.toml makes, it’s the package’s folder.

    Qualified names

    An Import also brings a module’s names in under its last name, so shapes.Square and report.Describe name the same things as Square and Describe. A qualified name tells two modules’ declarations of one name apart. Here text/plain.fb has a Describe of its own:

    shapes.fb
    Import std.core
    
    Public Struct Rect
        Public Field Width: Float
        Public Field Height: Float
    
        Public Method Area(): Float => Width * Height
    End Struct
    text/report.fb
    Import std.core
    Import "../shapes"
    
    Public Function Describe(r: Rect): String => "{r.Width} by {r.Height}, area {r.Area()}"
    text/plain.fb
    Import std.core
    Import "../shapes"
    
    Public Function Describe(r: Rect): String => "a rectangle"
    main.fb
    Import std.core
    Import "./shapes"
    Import "./text/report"
    Import "./text/plain"
    
    Local room = Rect(4, 3)
    Print(report.Describe(room))
    Print(plain.Describe(room))
    Output
    4 by 3, area 12
    a rectangle

    An unqualified Describe(room) would fit both, and is an error that says “the call to ‘Describe’ is ambiguous”, with a note at each of them.

    What you learned

    • Each .fb file is a module, and what it declares is private to it unless it’s Public.
    • Import "./path" imports a file by its path from the importing file, without .fb.
    • Only the main file has top-level code; the other files declare things.
    • An imported module’s names can be qualified by its last name, as report.Describe.

    Report an issue with this page or with FutureBlitz on GitHub.