Pointer types

In contrast with C++ or Rust Argentum pointers always point to class instance or interface instance. They cannot point to local variables or object fields. This is much like references in Java/Kotlin.

In contrast with Java and other GC-based languages Argentum provides multiple different pointer types. These pointer types have three purposes:

  • They let document your intensions on each particular parameter/field/result improving readability.
  • They let compiler automate multiple operations on objects.
  • They allow to check at compile time for the program actions that may produce wrong data structures.

Owning (composition) @pointer

Composition pointers are unique owners of mutable objects. They are so valuable, that they have a dedicated post: here.

They have syntax "@ClassName"

They are produced by two types of expressions:

  • new object instance creation: ClassName
  • copy of the existing object with unary prefix operator "@".
a = Array(Object); // Create a new instance of Array, so `a` has type @Array.
a.append(MyClass)  // Create a new instance of MyClass and pass it to the `append` method.
x = MyClass;       // Create a new instance of MyClass.
a.append(@x)       // Append to `a` a copy of `x`.

In line 4 we can't pass just "x" to the append method because "append" places its parameter in the array and if it would be possible, the array and the x variable would be owners of the same object. This would break the composition rules of single ownership and make all sorts of problems immanent to languages like Java that doesn't support composition. For example when user alters one element of the array these modifications would be suddenly reflected in another expected-to-be-separate object. That's why parameters should be @pointer i.e. totally separate instance of "MyClass" not owned elsewhere.

Composition @pointers are backbone of all data structures in computer world, they define trees of ownership in HTML/XML/JSON DOMs, they define scene graphs in UI and game engines, they define AST in compilers and structures of office documents. It's the main pointer type in argentum object fields:

class MyClass {
   items = Array(Item);  // @Array type
   style = Style;        // @Style type
   name = "";            // @String type
}
fn toString(i int) @String {        // Function returning a newly created string
   string_Builder.putInt(i).toStr() // Builder.toStr produces @String
                                    // that becomes the `toString` function result
}

When composition pointer is located in a function result, it has one meaning - it points to a newly created (or copied) object that can be directly assigned to a @field (or processed and returned further by the call stack).

BTW it is not allowed to pass @pointers as parameters. The reasoning behind this will be discussed in a separate post. The legit alternative is to pass lambdas returning @pointers.
Example:

class MyClass {
   name = String;
   setName1(a @String) {   // Error, @pointers cannot be parameters
     name := a;
   }
   setName2(a ()@String) { // Ok, lambda returning @String is legit
     name := a();          // Now call this lambda and store its @String result
   }
}

Bottom line: the @poiter syntax is a direct sign of a factory function or setter method. Something that deals with object creation.

Non-owning (associative) &pointer

In Argentum programming language, most of the pointers guarantee that the pointed-to object remains in existence as long as the pointer is pointing to it.

There is one exception: the weak-pointer. It is declared as &ClassName.

fn myFunction(wp &MyObject) ....

Also the expression &ClassName creates a disconnected weak pointer to the given class/interface. It is useful to declare fields:

class MyClass {
   peer = &MyClass; // weak pointer to MyClass, initially points nowhere
}

There is also a unary prefix operator "&" that creates these pointers from other pointer types:

x = MyClass;   // x is an @owning pointer, that holds a newly created instance of MyClass.
w = &x;        // w is a &weak pointer, that references the instance upheld by `x`.
w1 = &MyClass; // w1 is a &weak pointer to MyClass not bound to any object.
w1 := w;       // Now both w and w1 point to the same instance.
x := MyClass;  // Now x points to a new instance of MyClass,
               // the previous MyClass gets deleted,
               // w and w1 became not bound to objects.
w1 := &x;      // Make w1 point to a new object stored in x

Weak pointer fields are useful when it's needed to store a cross-relations between objects from different parts of object hierarchy without assumption of ownership. They can:

  • connect nodes in a graph
  • store relations between tables in database
  • connect a page element to its style in a web page or office document
  • make the smart connector of a diagram know the objects that define the coordinates of its endpoints
  • make formulas of spreadsheets reference other cells (in internal representation)
  • store references from the variable usage to variable definition in compiler AST
  • and may other applications.

Every piece of data that, when serialized to JSON or XML, requires using of "id/idref" or other type of naming, every time some arbitrary cross references involved, every time a UML diagram contains an arrow without diamonds - it's a "plain association", and Argentum &pointers are the "association" pointers.

These association &links exist as long as two objects are alive - the one that points and the one that is pointed-to.

Application can easily check if a &weak pointer is alive and take some temporary lock pointer out of it:

w1 ? _.someMethod(); // check if w1 is bound, and if it is, call its method

When used in a function parameter or return value the &pointer tells that the function is ok with loosing this object, is willing to check it on each usage or is going to assign it to some &field.

This is the only inherently nullable type of pointers, because it can drop connection by itself on target destruction.

Temporary lock pointer

Temporary lock pointers exist only in parameters/locals/temporary values/return values.
They cannot reside in object fields. Whey are used in almost all cases of code execution.
They are created when:

  • a &pointer is checked/locked with "?" operator
  • any @composition object field is been read

It's like temporary @composition pointer: as long as this pointer exists, the object it points-to is alive.

It's syntax is just a name of a class or interface "MyClass".

In the above example "w1 ? _.someMethod()" the "_" variable is a temporary lock pointer, that protects object from being deleted as long as someMethod is working.

Another example:

class MyObject {
  name = "asdf";
}
fn getName(MyObject o) String { // o is a lock pointer
   o.name                       // function returns a temp lock pointer to a string
}
a = MyObject;     // a is an owning pointer
n = getName(a);   // When passing a to a getName function, a lock pointer is created
                  // Now n is a lock pointer to String
a := MyObject;    // Previous MyObject from variable a was destroyed,
                  // but its name is still alive because it is retained by variable n

Lock pointers are work force of the Argentum language. They used every time an object needs to be passed to or from a function, or stored in a local variable or when a temporary value is needed.

Internally they are implemented as automated ref counters. But compiler uses some minor optimizations that reduces the amount of retain/release operations to bare minimum. And also, these counters will never be the atomic counters. Even in multithreaded environments. It will be discussed in a separate post. (In short: mutable objects always belong to one thread, they get passed across threads through queues that perform the cross-thread transfer only when object is not locked by temp pointers).

Conversion between pointers to mutable objects

From\To&weak-pointerlock-pointer@composition
&weakBy assignmentWith ? or : operator (or && or ||)Through lock ptr
with ? or : and then @copy operator
lockWith &operatorBy assignmentWith @copy operator
@compositionWith &operatorAutomatically by reading value
of @field or @variable
With @copy operator
or by returning @local
from function or {} block

Examples:

class MyClass {
   w = &MyClass;    // w is an empty &weak pointer to MyClass
}
x = MyClass;  // x is a composition ptr @Blob
x.w := &x;    // @composition -> &weak conversion
a = x;        // @composition -> lock-pointer auto-conversion by accessing @value
y = @x;       // @composition -> @composition conversion with @copy operator
x.w ? x:= @_; // if x.w is not empty, make a copy of it and assign to x
              // &weak(x.w) -> lock_ptr(_) conversion with ?operator
              // lock_ptr(_) -> @composition conversion with @copy operator
w = x.w;      // &weak -> &weak conversion by assignment

x := {
   temp = MyClass;  // temp of type @MyClass
   temp.w := &y;    // When accessing `temp` it reads as lock_ptr to MyClass
   temp             // When returned from {block} temp gives it the type of @MyClass
};

Examples with pictures

class MyClass {
   w = &MyClass;    // w is an empty &weak pointer to MyClass
}
x = MyClass;  // x is a composition ptr @Blob
classDiagram x_var *-- MyClass
x.w := &x;    // @composition_ptr -> &weak_ptr conversion
classDiagram class MyClass{ weak(MyClass) w } x_var *-- MyClass MyClass <-- MyClass
a = x;        // @composition_ptr -> lock_ptr auto-conversion by accessing a @value
classDiagram class MyClass{ weak(MyClass) w } x_var *-- MyClass MyClass <-- MyClass a_temp_var o-- MyClass
y = @x;  // @composition_ptr -> lock_ptr auto-conversion by accessing a @value
         // followed by lock_ptr -> @composition_ptr conversion with @copy operator
classDiagram class MyClass{ weak(MyClass) w } class MyClass_copy{ weak(MyClass) w } x_var *-- MyClass MyClass <-- MyClass a_temp_var o-- MyClass y_var *-- MyClass_copy MyClass_copy <-- MyClass_copy
x.w ? x:= @_; // if x.w is not empty, make a copy of it and assign to x
              // &weak(x.w) -> lock_ptr(_) conversion with ?operator
              // lock_ptr(_) -> @composition conversion with @copy operator
classDiagram class MyClass{ weak(MyClass) w } class MyClass_copy2{ weak(MyClass) w } class MyClass_copy{ weak(MyClass) w } x_var *-- MyClass_copy2 MyClass_copy2 <-- MyClass_copy2 a_temp_var o-- MyClass MyClass<--MyClass y_var *-- MyClass_copy MyClass_copy <-- MyClass_copy
w = x.w;      // &weak -> &weak conversion by assignment
classDiagram class MyClass{ weak(MyClass) w } class MyClass_copy2{ weak(MyClass) w } class MyClass_copy{ weak(MyClass) w } x_var *-- MyClass_copy2 MyClass_copy2 <-- MyClass_copy2 a_temp_var o-- MyClass MyClass<--MyClass y_var *-- MyClass_copy MyClass_copy <-- MyClass_copy w_var --> MyClass_copy2
x := {
   temp = MyClass;  // temp of type @MyClass
   temp.w := &y;    // When accessing `temp` it reads as lock_ptr to MyClass
   temp             // When returned from {block} temp gives it the type of @MyClass
};
classDiagram class MyClass{ weak(MyClass) w } class MyClass_instance1{ weak(MyClass) w } class MyClass_copy{ weak(MyClass) w } x_var *-- MyClass_instance1 MyClass_copy<-- MyClass_instance1 w_var --> nothing a_temp_var o-- MyClass MyClass<--MyClass y_var *-- MyClass_copy MyClass_copy <-- MyClass_copy

Conclusion

  • Two pointer types allowed in object fields: @composition pointer and &weak pointer are designed to hold mutable data structures of arbitrary complexity.
  • Lock-pointers allow safe access and easy processing of these data structures.
  • Copy-locking-releasing operations are implemented automatically. They performed on object hierarchies in safe manner, preserving composition and association invariants and ensuring memory safety.
  • Mutable pointer type declarations are easy to remember: they reflect operations used to produce these pointers: & - create weak, @ - create unique instance by copy.

Tutorial 2: Loop in Graph gives a practical example on how these three types of pointers work together.

Leave a Reply

Your email address will not be published. Required fields are marked *