Hello World

Every Fly program starts from a main() function. Import a module with import, then call its functions using the namespace prefix.

import fly.os

main() {
    os.print("Hello, World!")
}

Variables and types

Variables are declared with their type. An initial value is optional; uninitialized variables default to their zero value.

int count
int x = 10
bool ready = false
string name = "Fly"

Built-in types: bool, byte, short, ushort, int, uint, long, ulong, float, double, string.


Functions

Functions in Fly have no return type — all results are communicated through parameters.
Mark a parameter const to make it a read-only input. Leave it mutable to make it a writable output. Every parameter is passed by reference, so no data is ever copied.

double(const int n, int result) {
    result = n * 2
}

main() {
    int x
    double(21, x)   // x = 42
}

A function can write to multiple output parameters naturally:

minMax(const int a, const int b, int min, int max) {
    if (a < b) {
        min = a
        max = b
    } else {
        min = b
        max = a
    }
}

main() {
    int lo
    int hi
    minMax(3, 7, lo, hi)   // lo=3, hi=7
}

Importing modules

import fly.str          // use as str.len(...)
import fly.str.*        // use unqualified: len(...)
import fly.str as s     // use as s.len(...)

Error handling

Use fail inside a function to signal an error, with an optional numeric code and message. The error propagates automatically to the caller — no explicit rethrow is needed.

fetch(const string url) {
    if (url == "") {
        fail 404, "Not Found"
    }
}

Wrap calls in a handle block to capture any error into an error variable, which is populated automatically.

main() {
    error err handle {
        fetch("")
    }
    if (err) {
        // err is populated automatically — inspect code or message
    }
}

If you don't need to name the error, omit the variable:

main() {
    handle {
        fetch("")
    }
}

Structs

A struct holds data fields. It can extend one other struct. Structs have no virtual dispatch — access is direct and allocation-free.

struct Point {
    int x
    int y
}

struct Point3D : Point {
    int z
}

main() {
    Point3D p = new Point3D()
    p.x = 1
    p.y = 2
    p.z = 3
    delete p
}

Classes

A class adds virtual method dispatch via vtable. It can extend a struct (inheriting its fields) and implement one or more interfaces.

interface Drawable {
    draw()
}

class Circle : Point, Drawable {
    int radius

    draw() {
        // render the circle
    }
}

main() {
    Circle c = new Circle()
    c.x = 0
    c.y = 0
    c.radius = 5
    c.draw()
    delete c
}

Visibility modifiers for members: public, private, protected.
Use static for class-level fields and methods.
Use const on a method to mark the receiver as read-only.


Smart pointers

Instead of managing new/delete manually, use a smart allocation strategy.

KeywordOwnershipFreed when
new uniqueSingle ownerVariable goes out of scope
new sharedReference-countedLast owner exits scope
new weakNo ownership trackingFirst owner exits scope
process() {
    Point p = new unique Point()
    p.x = 10
    p.y = 42
}   // p freed automatically here — no delete needed

A unique object cannot be copied; shared copies increment the reference count.