Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Nested Data

Data modules can include other data modules as fields. This allows you to create nested structures, which are common in real-world programming. Here is an example of this concept in action:

use Core.print

data Point:
    i32 x;
    i32 y;
    Point(x, y);

data Rectangle:
    Point top_left;
    Point bottom_right;
    Rectangle(top_left, bottom_right);

def main():
    Point p1 = Point(0, 0);
    Point p2 = Point(10, 10);
    Rectangle rect = Rectangle(p1, p2);
    print($"rect.top_left.(x, y) = ({rect.top_left.x}, {rect.top_left.y})\n");
    print($"rect.bottom_right.(x, y) = ({rect.bottom_right.x}, {rect.bottom_right.y})\n");

This program will print these lines to the console:

rect.top_left.(x, y) = (0, 0)
rect.bottom_right.(x, y) = (10, 10)

Note that storing the Point variables in the rect variable through its constructor creates copies of the points. In other languages this would need to be done manually, but in Flint its automatic. So, when changing p1 and p2 after the creation of rect, the top_left and bottom_right fields will not be changed:

use Core.print

data Point:
    i32 x;
    i32 y;
    Point(x, y);

data Rectangle:
    Point top_left;
    Point bottom_right;
    Rectangle(top_left, bottom_right);

def main():
    Point p1 = Point(0, 0);
    Point p2 = Point(10, 10);
    Rectangle rect = Rectangle(p1, p2);

    print($"p1.(x, y) = ({p1.x}, {p1.y})\n");
    print($"p2.(x, y) = ({p2.x}, {p2.y})\n");
    print($"rect.top_left.(x, y) = ({rect.top_left.x}, {rect.top_left.y})\n");
    print($"rect.bottom_right.(x, y) = ({rect.bottom_right.x}, {rect.bottom_right.y})\n");

    print("\n");
    p1.(x, y) = (4, 5);
    p2.(x, y) = (22, 33);

    print($"p1.(x, y) = ({p1.x}, {p1.y})\n");
    print($"p2.(x, y) = ({p2.x}, {p2.y})\n");
    print($"rect.top_left.(x, y) = ({rect.top_left.x}, {rect.top_left.y})\n");
    print($"rect.bottom_right.(x, y) = ({rect.bottom_right.x}, {rect.bottom_right.y})\n");

This program will print these lines to the console:

p1.(x, y) = (0, 0)
p2.(x, y) = (10, 10)
rect.top_left.(x, y) = (0, 0)
rect.bottom_right.(x, y) = (10, 10)

p1.(x, y) = (4, 5)
p2.(x, y) = (22, 33)
rect.top_left.(x, y) = (0, 0)
rect.bottom_right.(x, y) = (10, 10)

What about Circular References?

The below example actually compiles, but its impossible to run.

Data is actually allowed to contain itself, but its impossible to initialize, as Flint has noo concept of nullpointers or null like other languages have. Flint has its optionals Opt<T> instead, but they are not implemented yet. Its amusing how the below example actually compiles fine.

Flint does not allow a data module to reference itself directly or indirectly like showcased below:

data Node:
    i32 value;
    Node next;
    Node(value, next);

While this may seem restrictive, it is pretty easy explained why this does not work: If you try to initialize a new variable of type Node you need to provide both its fields for the initializer. The value is fine, you can just pass in a literal, but what about the second field, next? To create a new variable of type Node you need an already existent variable of the same type to pass into, and thats impossible.

Hint:

Flint can handle circular references with the help of the optional type (Opt). These convert a reference to a wek reference in circular context's, thus enabling the use of data in of itself, for example for linked lists.