6.9 Structures

Users may also define their own data types as structures, along with user-defined operators, much as in C++. By default, structure members are public (may be read and modified anywhere in the code), but may be optionally declared restricted (readable anywhere but writeable only inside the structure where they are defined) or private (readable and writable only inside the structure). In a structure definition, the keyword this can be used as an expression to refer to the enclosing structure. Any code at the top-level scope within the structure is executed on initialization.

Variables hold references to structures. That is, in the example:

struct T {
  int x;
}

T foo;
T bar=foo;
bar.x=5;

The variable foo holds a reference to an instance of the structure T. When bar is assigned the value of foo, it too now holds a reference to the same instance as foo does. The assignment bar.x=5 changes the value of the field x in that instance, so that foo.x will also be equal to 5.

The expression new T creates a new instance of the structure T and returns a reference to that instance. In creating the new instance, any code in the body of the record definition is executed. For example:

int Tcount=0;
struct T {
  int x;
  ++Tcount;
}

T foo=new T;
T foo;

Here, new T produces a new instance of the class, which causes Tcount to be incremented, tracking the number of instances produced. The declarations T foo=new T and T foo are equivalent: the second form implicitly creates a new instance of T. That is, after the definition of a structure T, a variable of type T is initialized to a new instance (new T) by default. During the definition of the structure, however, variables of type T are initialized to null by default. This special behavior is to avoid infinite recursion of creating new instances in code such as

struct tree {
  int value;
  tree left;
  tree right;
}

The expression null can be cast to any structure type to yield a null reference, a reference that does not actually refer to any instance of the structure. Trying to use a field of a null reference will cause an error.

The function bool alias(T,T) checks to see if two structure references refer to the same instance of the structure (or both to null). In the example at the beginning of this section, alias(foo,bar) would return true, but alias(foo,new T) would return false, as new T creates a new instance of the structure T. The boolean operators == and != are by default equivalent to alias and !alias respectively, but may be overwritten for a particular type (for example, to do a deep comparison).

Here is a simple example that illustrates the use of structures:

struct S {
  real a=1;
  real f(real a) {return a+this.a;}
}

S s;                            // Initializes s with new S;

write(s.f(2));                  // Outputs 3

S operator + (S s1, S s2)
{
  S result;
  result.a=s1.a+s2.a;
  return result;
}

write((s+s).f(0));              // Outputs 2

It is often convenient to have functions that construct new instances of a structure. Say we have a Person structure:

struct Person {
  string firstname;
  string lastname;
}

Person joe;
joe.firstname="Joe";
joe.lastname="Jones";

Creating a new Person is a chore; it takes three lines to create a new instance and to initialize its fields (that’s still considerably less effort than creating a new person in real life, though).

We can reduce the work by defining operator init:

struct Person {
  string firstname;
  string lastname;

  void operator init(string firstname, string lastname) {
    this.firstname=firstname;
    this.lastname=lastname;
  }
}

Person joe=Person("Joe", "Jones");

The use of operator init to implicitly define constructors should not be confused with its use to define default values for variables (see Variable initializers). Indeed, in the first case, the return type of the operator init must be void while in the second, it must be the (non-void) type of the variable.

Much like in C++, casting (see Casts) provides for an elegant implementation of structure inheritance, including a virtual function v:

struct parent {
  real x;
  void operator init(int x) {this.x=x;}
  void v(int) {write(0);}
  void f() {v(1);}
}

void write(parent p) {write(p.x);}

struct child {
  parent parent;
  real y=3;
  void operator init(int x) {parent.operator init(x);}
  void v(int x) {write(x);}
  parent.v=v;
  void f()=parent.f;
}

parent operator cast(child child) {return child.parent;}

parent p=parent(1);
child c=child(2);

write(c);                       // Outputs 2;

p.f();                          // Outputs 0;
c.f();                          // Outputs 1;

write(c.parent.x);              // Outputs 2;
write(c.y);                     // Outputs 3;

For further examples of structures, see Legend and picture in the Asymptote base module plain.