Skip to content

Structures

IsaacShelton edited this page Nov 16, 2022 · 3 revisions

Structures

Structures allow for multiple data types to be combined together

Declaration

Structures are declared by using the struct keyword followed by a list of fields.

struct Employee (name String, age int, productivity float)

Usage Example

import basics

struct Tree (age int, height int)

func main {
    tree Tree
    tree.age = 12
    tree.height = 43
    
    print("tree.age  =>  " + tree.age)
    print("tree.height  =>  " + tree.height)
}
tree.age  =>  12
tree.height  =>  43

Reducing Redundancy

Sometimes when declaring structures, multiple fields in a row will have the same type repeated for each of them.

struct Person (firstname String, lastname String, occupation String)

When this is the case, you can omit all of the repeated types expect the final one in order to simplify the definition.

struct Person (firstname, lastname, occupation String)

Embedded Structures

Sometimes it's desired to share the same fields as another struct. In order to make this easier, you can include fields of an already defined structure into a new one using the struct keyword followed by the name of the structure.

struct Human (age int)
struct Citizen (struct Human, name String)
struct Doctor (struct Citizen, experience int, salary ulong)

If the included structure comes first in the field list, then the newly created structure will also be valid as the included structure when taken as a pointer and casted.

import basics

struct Point2D (x, y float)
struct Point3D (struct Point2D, z float)

func distanceToOrigin(p Point2D) float {
    return sqrtf(p.x * p.x + p.y * p.y)
}

func main {
    point Point3D
    point.x = 3.0f
    point.y = 4.0f
    point.z = 2.0f
    
    xy_distance float = distanceToOrigin(*cast *Point2D &point)
    
    print("2D distance to origin  =>  " + xy_distance)
}
2D distance to origin  =>  5.0

Structure Domains

Structure definitions can optionally be followed by a structure domain, in which all function declarations inside will be made into methods

import basics

struct Person (firstname, lastname String) {
    func getFullname String {
        return this.firstname + " " + this.lastname
    }
}

func main {
    person Person
    person.firstname = "Isaac"
    person.lastname = "Shelton"
    
    print("Person's name is: " + person.getFullname())
}
Person's name is: Isaac Shelton

Packed Structures

By default, structures contain padding in order to improve access performance.

This can be disabled using the packed keyword.

packed struct MyFileHeader (magic ushort, version uint, offset1, offset2 usize)

Records

Records are structures that the compiler will automatically generate a constructor for.

Records are usually preferrable for holding data over regular structures.

record Person (firstname, lastname String, age int)

func main {
    person Person = Person("Bekah", "Armstrong", 19)
    printf("%S %S is %d years old\n", person.firstname, person.lastname, person.age)
}

See records for more information

Anonymous Structures

Structure types can also be used anonymously without a name

import basics
import math

func main {
    point struct (x, y, z float)

    point.x = 1.0f
    point.y = 2.0f
    point.z = 3.0f

    printf("Distance to origin is %hf\n", distanceToOrigin(point))
}

func distanceToOrigin(p struct (x, y, z float)) float {
    return sqrt(p.x * p.x + p.y * p.y + p.z * p.z)
}

In order for two anonymous structure types to be considered compatible, they must have the same fields and the same structural layout.

For example:

The types struct (x, y float) and struct (u, v float) are incompatible.

And also the types struct (a, b int) and struct (b, a int) are incompatible.

Anonymous Composite Fields

Structs and unions can be used as anonymous wrappers for fields.

struct FloatOrIntVec3 (
    is_floats bool,
    union (
        struct (xf, yf, zf float),
        struct (xi, yi, zi int)
    )
)

In the above example, the type FloatOrIntVec3 has 7 fields, some of which overlap in memory:

  • is_floats
  • xf
  • yf
  • zf
  • xi
  • yi
  • zi

However there's an anonymous union of two anonymous structs, which are unnamed, so the names of the sub fields are promoted to be directly accessible from the next higher name scope.

Because of this, we can access members like xf directly instead of having to go through a field.

struct FloatOrIntVec3 (
    is_floats bool,
    union (
        struct (xf, yf, zf float),
        struct (xi, yi, zi int)
    )
)

// Can access 'xf' via 'this.xf' where 'this' is a '*FloatOrIntVec3'
// since the union field is unnamed and also the struct field of type `struct (xf, xy, xz float)` is unnamed

versus

struct FloatOrIntVec3 (
    is_floats bool,
    data union (
        float_info struct (xf, yf, zf float),
        int_info struct (xi, yi, zi int)
    )
)

// We have to access 'xf' via 'this.data.float_info.xf' (where 'this' is a '*FloatOrIntVec3')
// since the union field 'data' is named and also the struct field 'float_info' is named

Classes

Classes are structures which can have virtual methods defined on them.

import basics

class Shape () {
    constructor {}

    virtual func getArea() float = 0.0
}

class Rectangle extends Shape (w, h float) {
    constructor(w, h float){
        this.w = w
        this.h = h
    }

    override func getArea() float = this.w * this.h
}

func main {
    shape *Shape = new Rectangle(5, 10)
    defer delete shape
    print(shape.getArea())
}

See classes for more information.

Clone this wiki locally