1. Cast, and dynamic typing

An object instance of a heir class can be casted to an object instance of an ancestor class. Unlike, casting of integer values, (e.g. i32 to i64), because an object is an aliasable type, the memory size of the object is not modified. Casting must respect mutability of the object value. Moreover, this cast can be made implicitely, as it does not create any problem in memory. In the following example, the class Bar inherits from the class Foo. A variable x is created, and is of type &Bar. At line 1, an implicit cast is made of a &Bar value to a &Foo value, the same cast is made but explicitely at line 1.

import std::io;

class Foo {
    pub self () {}

    pub def foo (self) {
        println ("Foo");
    }
}

class Bar over Foo {
    pub self () {}

    pub over foo (self) {
        println ("Bar");
    }
}

def baz (f : &Foo) {
    f.foo ();
}

def main () {
    let x = Bar::new ();
    baz (x);

    let y = cast!{&Foo} (x);
    y.foo ();
}


Results:

Bar
Bar

1.1. Dynamic typeinfo

One can note from the above example, that when a variable contains a value of type &Foo, that does not necessarily mean that this is a pure &Foo value, but it can be a &Bar. In object oriented programming, this principle is denote polyphormism. In the chapter Basic programming concepts, we have seen that every object has specific attributes. Object is no exception to the rule. The following table lists the default specific attributes of the object types.

Name Meaning
typeid The name of the type stored in a value of type [c32]
typeinfo A structure of type TypeInfo, containing information about the type

These attributes are compile time executed, and thus are static. For example, in the following source code, the typeid of the class Bar is printed to stdout, followed by a line that does exactly the same thing (literraly).

def main () {
    println (Bar::typeid);
    println ("main::Bar");
}


When it comes to dynamic typing, it can be interesting to get the typeinfo of the type of the value that is actually stored inside the variable (e.g. get the typeinfo of the Bar class, type of the value contained inside a &Foo variable). To do that, the specific attribute typeinfo of object is also accessible from the value directly, and this time dynamically.

def main () {
    let x = Bar::new ();
    let y : &Foo = x;

    println (y::typeinfo);
}


Results:

core::typeinfo::TypeInfo(13, 16, [core::typeinfo::TypeInfo(13, 16, [], main::Foo)], main::Bar)


The result presented above, gives the following information : 1) we have a object, 2) its size is 16 bytes, 3) it has an ancestor (object, size 16 bytes, no ancestor, named main::Foo), 4) its name is main::Bar.

The TypeInfo returned by the typeinfo attributes (either dynamically or statically), is a structure whose definition is the following. The inner fields depend on the value of the typeid field, for example, when dealing with an object it stores the ancestor TypeInfo, and when dealing with slice, it stores the TypeInfo of the type contained inside the slice.

pub struct
| typeid : TypeIDs
| size   : usize
| inner  : [TypeInfo]
| name   : [c32] 
 -> TypeInfo;

pub enum : u32
| ARRAY        = 1u32
| BOOL         = 2u32
| CHAR         = 3u32
| CLOSURE      = 4u32
| FLOAT        = 5u32
| FUNC_PTR     = 6u32
| SIGNED_INT   = 7u32
| UNSIGNED_INT = 8u32
| POINTER      = 9u32
| SLICE        = 10u32
| STRUCT       = 11u32
| TUPLE        = 12u32
| OBJECT       = 13u32
| VOID         = 14u32
 -> TypeIDs;


The typeinfo of a class is stored in the text and is accessible from the vtable of the object. One can note that Bar and Foo have a size of 16 bytes, despite the fact that they store no fields. This is due to two pointers that are stored inside every objects, the first pointer is refering to the monitor of the object (cf. Parallelism), and the second one points the the vtable of the object.

1.2. Object

Ymir have a type named Object, that can used to cast any object into that type. The reverse is impossible. We have seen that the object are not inheriting from a global ancestor, and this is really not the case. This cast unlike casting to parent class objects, cannot be made implicitely. We can see the &(Object) type as the &(void) type, that can store any pointer, but for objects. Unlike &(void) (in which by the way we can't cast objects), &Object stores one valuable information, it stores a valid object value, with a vtable, a monitor, a typeinfo, and cannot be null.

In the following example, a pattern matching is used to check the type of the object that is returned by the foo function. This is discussed in chapter Pattern Matching.

def foo ()-> &Object {
    cast!{&Object} (Foo::new ())
}

def main () {
    match foo () {
        Foo () => {
            println ("I got a Foo !");
        }
    }
}


Results:

I got a Foo !


Some function of the standard library uses the Object type to return values, when it is impossible statically to get more accurate information about the type (e.g. [Packable] (https://gnu-ymir.github.io/Documentations/en/traits/serialize.html).)

results matching ""

    No results matching ""