3. Declarations

In this chapter we will discuss some of the different ways to declare symbols in Pike in detail. We have seen examples of many of these, but we haven't really gone into how they work.

3.1. Variables

A variable represents a place to store a value that may be changed. There are two main locations where variables are stored; either in an object (this is where variables declared outside of functions are stored) or on the stack (this is where variables declared in functions are stored). For variables in objects their lifetime is the same as that of their object. For variables on the stack their lifetime typically ends when the function returns, but there are exceptions (eg trampolines, generator functions and asynchronous functions).

When declaring a variable, you also have to specify what type of variable it is. For most types, such as int and string this is very easy. But there are much more interesting ways to declare variables than that, let's look at a few examples:

int x; // x is an integer
int|string x; // x is a string or an integer
array(string) x; // x is an array of strings
array x; // x is an array of mixed
mixed x; // x can be any type
string *x; // x is an array of strings

// x is a mapping from int to string
mapping(string:int) x;

// x implements Stdio.File
Stdio.File x;

// x implements Stdio.File
object(Stdio.File) x;

// x is a function that takes two integer
// arguments and returns a string
function(int,int:string) x;

// x is a function taking any amount of
// integer arguments and returns nothing.
function(int...:void) x;

// x is ... complicated
mapping(string:function(string|int...:mapping(string:array(string)))) x;
    

As you can see there are some interesting ways to specify types. Here is a list of what is possible:

mixed
This means that the variable can contain any type, or the function return any value.
array( type )
This means an array of elements with the type type.
mapping( key type : value type )
This is a mapping where the keys are of type key type and the values of value type.
multiset ( type )
This means a multiset containing values of the type type.
object ( program )
This means an object which 'implements' the specified program. The program can be a class, a constant, or a string. If the program is a string it will be casted to a program first. See the documentation for inherit for more information about this casting. The compiler will assume that any function or variable accessed in this object has the same type information as that function or variable has in program.
program
This too means 'an object which implements program'. program can be a class or a constant.
function( argument types : return type )
This is a function taking the specified arguments and returning return type. The argument types is a comma separated list of types that specify the arguments. The argument list can also end with ... to signify that there can be any amount of the last type.
type1 | type2
This means either type1 or type2
void
Void can only be used in certain places, if used as return type for a function it means that the function does not return a value. If used in the argument list for a function it means that that argument can be omitted. Example: function(int|void:void) this means a function that may or may not take an integer argument and does not return a value.

3.2. Functions

Functions are declared with a syntax similar to that of variables, but the symbol name is followed by a parenthesized comma-separated list of variables representing the function arguments, and the initial type representing the type of the returned value.

3.2.1. Basic declaration of functions

When a function is called, the interpreter sets this and this_object() to the object in which the function is located and proceeds to execute the function it points to. Also note that function pointers can be passed around just like any other data type:

        int foo() { return 1; }
        function bar() { return foo; }
        int gazonk() { return foo(); }
        int teleledningsanka() { return bar()(); }
      

In this example, the function bar returns a pointer to the function foo. No indexing is necessary since the function foo is located in the same object. The function gazonk simply calls foo. However, note that the word foo in that function is an expression returning a function pointer that is then called. To further illustrate this, foo has been replaced by bar() in the function teleledningsanka.

For convenience, there is also a simple way to write a function inside another function. To do this you use the lambda keyword. The syntax is the same as for a normal function, except you write lambda instead of the function name:

        lambda ( types ) { statements }
      

The major difference is that this is an expression that can be used inside an other function. Example:

        function bar() { return lambda() { return 1; }; )
      

This is the same as the first two lines in the previous example, the keyword lambda allows you to write the function inside bar.

Arguments to functions may be declared to be optional. This is done by specifying that the argument may be void. Omitted arguments evaluate to UNDEFINED.

        int foo(int arg1, int|void arg2) {
          if (undefinedp(arg2)) arg2 = arg1 - 1;
          return arg1 + arg2;
        }
      

It is also possible to specify that an optional argument has a default value other than UNDEFINED. The above example can also be written as:

        int foo(int arg1, int arg2 = arg1 - 1) {
          return arg1 + arg2;
        }
      

3.2.2. Variant functions

Similar to C++ and Java you can use function overloading in Pike. This means that you can have one function called 'foo' which takes an integer argument and another function 'foo' which takes a float argument.

Unlike C++ and Java you must however declare that this is your intention. Note also that the compiler must be able to differentiate between the variants (most notably, it does currently (Pike 9.0) not differentiate between different object types for the same argument position).

        class Foo {
          int foo(int a) { return a*3; }
          variant float foo(float b) { return b/3.0; }
        }
      

The above example results in a function named foo that has the type function(int:int)|function(float:float).

You may have noticed that the first foo function above did not have the variant keyword. This makes a difference when you have inherited the same symbol.

        class Bar {
          inherit Foo;
          int foo(int a) { return ::foo(a) + a; }
        }
        class Baz {
          inherit Foo;
          variant int foo(int a) { return ::foo(a) + a; }
        }
      

In the above example, Bar()->foo has the type function(int:int), while Baz()->foo has the type function(int:int)|function(float:float).

3.3. Constants

A constant is a symbol that will not change its value during runtime. It may however be changed via overriding via inherit.

There are several ways to declare constant symbols in a program in Pike.

3.3.1. constant

        constant message = "Hello, world!";
      

The above makes the symbol message have the constant value "Hello, world!".

3.3.2. enum

Enums can be used to declare multiple constants in a convenient way:

        enum Level {
          LOW = 0,
          MEDIUM,
          HIGH,
        }
      

The above declares 4 constants; LOW, MEDIUM and HIGH with the respective values 0, 1 and 2, as well as Level with the type value int(0..3). This makes Level a suitable type to use for values intended to hold one of LOW, MEDIUM or HIGH.

3.3.3. typedef

A typedef is a syntax to declare an alias for a type.

        typedef Level|void OptionalLevel;
      

A typedefed symbol (eg OptionalLevel above) may be used anywhere that a class name may be used.

3.4. Classes

A class (aka program groups a set of symbols (variables, functions and constants, etc) into a single unit and can be used as a template to create objects implementing the class.

3.4.1. Inheritance

The functionality of a class may be extended by a new class by means of inheritance.

        class Foo {
          int a;
        }
        class Bar {
          inherit Foo;
          int b;
        }
      

In the above example, the class Bar contains all the symbols of Foo, and also the variable b. Exactly what symbols that are inherited can be controlled by modifiers (see further below).

3.4.2. LFUNs

LFUns are functions that are called by various operators and global functions (aka EFUNs) when they operate on objects. They are typically declared protected in order to only be used by the corresponding operator or function.

The most common LFUN is create, which is called at object creation. It is typically used to initialize the object to a desired initial state.

        class Foo {
          int a;
          protected void create(int initial_a) { a = initial_a; }
        }
      

In the above example a call of Foo(17) results in an object of type Foo where the variable a has been set to 17.

As can be seen from the example above create often consists of just initializing some variables directly from its arguments. This can also be simplified by using the implicit create syntax:

        class Foo(int a){}
      

The above is equivalent to the previous declaration of Foo.

Other common LFUNs are _destruct, _sprintf and _get_iterator. See the Special object functions chapter for details.

3.4.3. Getters and setters

Sometimes you may want to have a symbol run some code every time it is accessed or modified. Then a getter/setter may be appropriate.

        int foo;
        int `bar() { return ~foo; }          // Getter for bar.
        void `bar=(int val) { foo = ~val; }  // Setter for bar.
      

The above will add a symbol bar that will evaluate to ~foo everytime it is accessed, and will set foo to the inverted value when ever it is set.

3.4.4. Generic types

To improve the type-safety over just using mixed, it is possible to define place-holder types that are replaced with actual types when the class is used.

        class Container (<T>) (T|void content) {}

        Container(<int>) int_container = Container(<int>)(17);
        Container(<float>) float_container = Container(<float>)(17.0);
        Container mixed_container = Container("foo");
      

The above class is similar to:

        class Container(mixed|void content) {}
      

But the former allows the compiler to eg check that only values of the expected types are put in int_container and float_container.

3.5. Modifiers

Modifiers are keywords that may be specified before the type of a symbol declaration or inherit. They typically affect the symbol lookup or other related compiler behavior.

3.5.1. protected

The protected modifier hides symbols from external indexing (ie they are still accessable to subclassess that have inherited the class, but not via predef::`->() or predef::`[]()).

In ancient versions of Pike this modifier was known as static.

3.5.2. local

The local modifier causes use of the symbol from the current class to not be affected by overloading by subclasses.

This modifier is also available with the name inline.

The modifier final is similar.

3.5.3. private

The private modifier hides symbols from internal indexing (ie they are not accessable to subclasses) and implies protected and local.

3.5.4. final

The final modifier causes the compiler to issue an error if a subclass attempts to overload the symbol.

The modifier local is similar, but more permissive.

Note in ancient versions of Pike this modifier was also available with the name nomask.

3.5.5. optional

The optional modifier causes the type checker to consider the symbol as optional to implement to satisfy the API (albeit if the symbol exists it still must comply with the type).

3.5.6. extern

The extern modifier indicates that a symbol may be implemented by a subclass, but does not define it in the current class. It implies optional.

3.5.7. public

The public modifier causes inherited private symbols to become localprotected (and thus available to both the inheriting class and subsequent inherits, albeit not overrideable or visible via external indexing). All other inherited symbols retain their modifiers.

Note that the public modifier is only useful for inherit statements. In all other cases it is essentially a no-op.

3.5.8. variant

The variant modifier is used to provide alternative APIs for functions. The different functions will be called depending on what the arguments are when the symbol is called.

3.5.9. __weak__

The __weak__ modifier is used to indicate to the garbage collector that it may clear the variable if it is the only holder of the value.

Note that this modifier is new in Pike 9.0.

3.5.10. __unused__

The __unused__ modifier is used to inhibit the warning that the symbol is not used.

Note that this modifier is new in Pike 9.0.

3.5.11. __generator__

The __generator__ modifier converts a function into a function that returns a restartable function.

        __generator__ int counter(int start, int stop)
        {
          while (start < stop) {
            continue return start++;
          }
          return stop;
        }
      

The above behaves similar to:

        function(:int) counter(int start, int stop)
        {
          return lambda() {
            if (start <= stop) {
              return start++;
            }
            return UNDEFINED;
          };
        }
      

Note that this modifier is new in Pike 9.0.

3.5.12. __async__

The __async__ modifier converts a function into an asynchronous function. For such functions an implicit Concurrent.Promise object is allocated, and all returns and yields are converted into setting the promise followed by returning UNDEFINED, except for the first return or yield which will return the Concurrent.Future corresponding to the promise.

        __async__ int foo(mixed ... args)
        {
          return 17;
        }
      

The above behaves similar to:

        Concurrent.Future(<int>) foo(mixed ... args)
        {
          Concurrent.Promise(<int>) __async_promise__ =
            Concurrent.Promise(<int>)();

          __generator__ lambda() {
            __async_promise__->failure(catch {
                __async_promise__->success(17);
                return UNDEFINED;
              });
            return UNDEFINED;
          }()();

          return __async_promise__->future();
        }
      

Restartable functions are required in order to be able to use predef::await().

Note that this modifier is new in Pike 9.0.

3.5.13. static

The static modifier is currently identical to the protected modifier except for warning that it is being used (as of Pike 9.0).

Note: In a future version of Pike this may change. Do not use.