15: Structs ∕ Ref Structs - royal-lang/rl GitHub Wiki

There are two types of structs in Royal. Regular structs known from C that are just plain data and then ref structs which are similar to classes from other languages.

ref structs are different though and can be both value and reference types (despite the ref in their type name.)

Structs

A struct in Royal is different from structs in other languages in that it can actually have inheritance and supports interfaces.

A plain old struct will always be a value type and inheritance doesn't work like ref struct inheritance in that it just copies the members from its inheritance into itself.

There is no base-struct type available as a member etc. which means you cannot cast it to its base-struct.

struct NAME
{
    ...
}

struct NAME : INHERITANCE
{
    ...
}

Example:

struct PacketHeader
{
    var ushort size;
    var ushort id;
}

struct MessagePacket : PacketHeader
{
    var uint messageSize;
    var string message;
}
var const s = "Hello World"!;

var MessagePacket message;
message.size = (2 + 2 + 4 + s.length);
message.id = 1001;
message.messageSize = s.length:uint;
message.message = s;

Structs in Royal can be implicitly converted to byte arrays and byte arrays can implicitly be converted to structs.

Example:

var byte[] buffer = message;

message = buffer;

Ref Structs

A ref struct similar to classes in other languages. The key difference is that it's not always a reference type in Royal. Whether it's a value type or a reference type depends entirely on how it's used and implemented, what type of data it holds and the size of its data. It is however best to assume that a ref struct will be a reference type because it will be in most cases where it's used differently than an actual struct.

Unlike structs then ref structs cannot be implicitly converted to byte arrays and byte arrays cannot be converted to ref structs either.

Royal only supports single inheritance, but has no limit on amount of interfaces inherited.

ref struct NAME
{
    ...
}

ref struct NAME : INHERITANCE
{
    ...
}

Example:

ref struct Bar
{
    private:
    var string _message;

    protected:
    this(string message)
    {
        _message = message;
    }

    public:
    prop string:const message
    {
        get { return _message; }
    }
}

ref struct Foo : Bar
{
    private:
    var string _name;

    public:
    this(string name, string message)
    {
        super(message);
    }

    prop string:const name
    {
        get { return _name; }
    }
}

To create a new ref struct you need to use the @= operator which creates a new instance.

var foo @= Foo("Jane Doe", "Hello World!");

writeln("Name: %s, Message: %s", foo.name, foo.message);

Using the .super property of a ref struct will access the ref struct's parent.

var bar = foo.super;

Virtual Functions

A virtual function is a function that may be override by any child instances.

Virtual functions are only supported by ref structs.

virtual fn NAME()
{
    ...
}

... (Other Royal Function Definitions)
ref struct Bar
{
    public:
    virtual fn baz()
    {
        writeln("Bar");
    }
}

ref struct Foo : Bar
{
    override fn baz()
    {
        writeln("Foo");

        super.baz();
    }
}

ref struct Boo : Bar
{
    override fn baz()
    {
        writeln("Boo");
    }
}

...

var foo @= Foo;
var boo @= Boo;

foo.baz();
boo.baz();

Output:

Foo
Bar
Boo

Abstract Functions

Abstract functions are similar to virtual functions. The key difference is that they don't need an initial body and the first child of its inheritance tree must implement it.

You don't need the override keyword for abstract functions unless you override a parent implementation of it.

Just like virtual functions then abstract functions are only supported by ref structs.

abstract fn NAME();

... (Other Royal Function Definitions)
ref struct Bar
{
    public:
    abstract fn baz();
}

ref struct Foo : Bar
{
    fn baz()
    {
        writeln("Foo");
    }
}

ref struct Boo : Bar
{
    fn baz()
    {
        writeln("Boo");
    }
}

...

var foo @= Foo;
var boo @= Boo;

foo.baz();
boo.baz();

Output:

Foo
Boo

Constructors

A constructor is used to initialize a type, whether a struct or ref struct.

For a struct if no constructor has been declared then it will have a default constructor that takes each of its fields as parameters in the order they were declared.

For a ref struct if no constructor has been declared then it will have a default constructor that takes no parameters.

A struct cannot have a default constructor with no parameters.

ref struct Foo
{
    this()
    {
        ...
    }
}

Just like constructors a struct can also have a destructor which will be called when the struct is getting freed for memory.

Destructors are declared using the destroy keyword.

You cannot allocate memory in a destructor.

ref struct Foo
{
    destroy()
    {
        ...
    }
}