The Struct package helps to build perl object classes. The objects store their data members in an array. The data accesors have the standard method call syntax, but in the most cases the method call is replaced by the random access to an array element. This makes the Struct based classes much more efficient than the classical solution based on hashes, and, unlike the fields module, does not require special typed lexical variables.

We introduce the features of the Struct package by examples of increasing complexity.

Basic usage

package Example; use Struct ( '$x', # scalar data member, initialised with "" '@y', # array data member, initialized with [ ] '%z', # hash data member, initialized with { } ); $e=new Example; $e->x=123; # scalar member accessor returns a lvalue push @{$e->y}, 456; # modify the array $e->z->{ABC}=789; $e->y=[ 456 ]; # replace the array completely $e->z={ ABC => 789 };

This is the most simple case. Each object of the class Example consists of three data members. The constructor new is generated automatically. It initializes the data members with appropriate default values.

The type signs $, @, and % in the use operator are only needed to choose the correct default value. The access methods have the same syntax independently from the member type. For array types they always return a reference, which can be either dereferenced or assigned to, completely replacing the array data member.

The data member accessors can also be called indirectly, that is, with member name obtained from an expression:

$field="x"; $e->$field=123;

Only in this case the access operation will have the full costs of a method call. In the case of a constant member name the member call operation is executed only once and then replaced by a much more efficient array access operation.

Note that there is no automatically generated destructor. If you need one, you must define it yourself.

Constructor with initialization

package Example; use Struct ( [ '$x' => '123' ], [ '@y' => '( 456 )' ], [ '%z' => '{ ABC => 789 }' ], );

This time the generated constructor will assign the given initial values to the data members. You can freely mix the explicitly initialized members with default-initialized.

The initial values don't need to be constants, they can be expressions of arbitrary complexity. But they must be always quoted, if their evaluation should be postponed till the constructor execution. To make the difference clear, here two further examples:

my $global=1; package Class1; use Struct [ '$x' => '$global' ]; package Class2; use Struct [ '$x' => $global ]; package main; $global=2; $c1=new Class1; $c2=new Class2;

Now $c1->x == 2 and $c2->x == 1.

Constructor with parameters

package Example; use Struct ( [ new => '$;$' ], [ '$x' => '#1' ], [ '$y' => '#2 || "some default"' ], );

The special keyword new introduces the prototype for the generated constructor. It is a standard perl subroutine prototype. The initial values may contain tokens of the form #i referring to the i-th argument of the constructor call. The arguments are counted starting with 1, since the leading argument is, as usual, the package name. As shown in this example, the argument references may stand alone or be involved in expressions.

Constructor with extra functionality

package Example; use Struct ... # as above sub new { my $this=&_new; ... # some special code return $this; }

If you need to perform some additional operations in the constructor which do not fit well in the initial expressions, you can simply redefine the constructor and put all the special stuff there. The automatically generated code with initializers is still available as _new, which should be called using & syntax, that is, without creating a new argument list @_. _new removes the package name from @_.

Constructor with keyword arguments

package Example; use Struct ( [ new => '$%' ], [ '$x' => '#1' ], [ '$y' => '##' ], '$u', '$v', '$w', '$z', ); my $e=new Example(1, z => 2, v => 'abc');

If the constructor prototype finishes with the character %, the generated constructor will accept the keyword => value pairs for all data members. The members omitted in the constructor call will be initialized with default values according to their type.

You can mix positional and keyword arguments in a constructor, as shown in the example. The data members initialized with positional arguments or with a special initializer ## are excluded from keyword lookup. Specifying their names in the constructor call will cause an exception, exactly the same as if the keywords were unknown.

Keywords and access filters

package Example; use Struct ( [ new => '%' ], [ '$x' => 'convert_x( #% )' ], [ '$y' => 'convert_y( #% )', '"default_y"' ], );

If you build the constructor with keyword arguments, you can also specify converter expressions for some data members. Then the data member will be initialized with the result of this expression. The special token #% is replaced by the two-element list member_name => value as given to the constructor. You may also specify an optional default value which will be used for initialization if the keyword does not occur among the constructor arguments.

The converter and default expressions may do whatever appropriate, and even raise an exception if the passed value does not pass some consistency checks.

Another interesting features of the converter expressions is that they are applied not only to the constructor arguments, but also in the assignments to the data members. This way, for an object defined as in example above,

$e->x=123;

will be automatically translated into

$e->x=convert_x(x => 123);

The translation will be done whereever the assignment occurs, not only in the own package, and even if the accessor name is specified indirectly.

Aliases for members

package Example; use Struct ( '$x | X', );

An object of this class has one data member, accessible under two names as $obj->x or $obj->X.

A member may have arbitrary many aliases, separated in the declaration by bars | and optional white spaces. Note that the type character (in this example $) must be specified only once, in front of the first name.

An aliased member may have all the kinds of initializers and access filters described above. If the data member is initialized via keyword argument, any of the aliases may appear in the argument list, and the used one will be passed to the access filter. But specifying several aliases of the same member in a constructor call is vorbidden and causes an exception.

Derived classes

Classes built with Struct may have superclasses and be derived as usual. However, there is one restriction: Struct does not support multiple inheritance. You may specify several parent classes, but at most one of them may be in its turn built with Struct. All other ancestors must be dataless abstract classes (aka interfaces).

If you are deriving from a Struct based class and want to add new data members, you must use the special syntax in the Struct parameter list; simply putting the parent class name in @ISA won't work:

package Example2; use Struct ( [ '@ISA' => 'Example' ], '$z', );

Inheriting static class members

If a class definition lies in a scope with namespace mode enabled, Struct recognizes this and puts the parent class in the lookup list of the current class. This way all parent's functions and variables are visible to the derived class without qualification, like static class members in C++.

Overriding the inherited initializers

Sometimes the constructor for the derived class needs to initialize the base class data members differently from the inherited constructor. Or it must get more arguments than the inherited one, and therefore its prototype must be changed. Both can be done by specifying new prototype and/or initializers directly after the ISA setting:

package Example2; use Struct ( [ '@ISA' => 'Example' ], [ new => '$%' ], [ '$x' => 'other_convert( #% )', "other_default_x" ], [ '$z' => '#1' ], );

The $y initializer is the same as in the base class Example. It is important to put all overriding initializers to the beginning of the parameter list, otherwise Struct will complain about duplicate field names.

Additional methods

A class built with Struct automatically gets the following methods additionally to the constructor and memember accessors.

_new
as already mentioned above, the automatically generated constructor is available under two names, new and _new. If you redefine the constructor, you can still access the generated code via this alias
sizeof
return the number of data members = the length of the array representing an object