Pointers

A pointer is a type which doesn't hold a value itself. Instead, it "points to" another value. If you are familiar with any C-like language, you are (hopefully) well aware of why pointers are nice, and you also know that they can be dangerous. In P*, pointers are not dangerous, and errors you make are detected without having programs crashing badly.

Let's begin with an example. We want to pass a value into a function, and the function should do something with it. We do not, however, want the value to be copied as we want to keep the result. This is how you could do it without pointers:

SCENE main {
	int move_to_neighbour(int i) {
		return i + 1;
	}

	int my_house_number = 134;

	/* Gives 134 */
	echo "I live in house number $my_house_number\n";

	my_house_number = move_to_neighbour(134);

	/* Gives 135 */
	echo "I have now moved, and live with my neighbour in number $my_house_number\n";
}

Easy or what? No need for pointers here. But what if we want to modify two values? A function can only return one value, so this might be a problem. But what if we put the values into an array? Surely a function can return an array with many values.

The stupid example

SCENE main {
	array<int> move_to_neighbour(array<int> values) {
		values[0] += 1;
		values[1] += 100;

		return values;
	}

	int my_house_number = 134;
	int my_phone_number = 5550453;

	/* Gives 134 and 5550453 */
	echo "I live in house number $my_house_number\n";
	echo "My phone number is $my_phone_number\n";

	array<int> my_info;
	my_house_number, my_phone_number => my_info;

	my_info = move_to_neighbour(my_info);

	my_house_number = my_info[0];
	my_phone_number = my_info[1];

	/* Gives 135 and 5550553 */
	echo "After moving, I live in house number $my_house_number\n";
	echo "After moving, my phone number is $my_phone_number\n";
}

OK, let's state that this works, but the readability is horrendous.

The big example

What we need is pointers! Code with pointers in have strange stuff in it, like &, <int> and *

SCENE main {
	void move_to_neighbour(pointer<int> house_number, pointer<int> phone_number) {
		*house_number += 1;
		*phone_number += 100;
	}

	int my_house_number = 134;
	int my_phone_number = 5550453;

	/* Gives 134 and 5550453 */
	echo "I live in house number $my_house_number\n";
	echo "My phone number is $my_phone_number\n";

	move_to_neighbour (&my_house_number, &my_phone_number);

	/* Gives 135 and 5550553 */
	echo "After moving, I live in house number $my_house_number\n";
	echo "After moving, my phone number is $my_phone_number\n";
}

A narrow-down

So, what's really going on here? Let us first talk about how a pointer is used in an easier example.

SCENE main {
	int my_value = 1;
	pointer<int> my_pointer = &my_value;
	echo "The value is " . *my_pointer . "\n";
}

We first declare an int value called my_value and assign the value 1 to it. Pointers are template types, that means that they can only point to values of one type. You choose this type by putting it's name between < and >. In theory, the pointers wouldn't have to only work with one type, but we do this to help prevent programming mistakes.

Then, we have the &my_value statement. The & is an operator which we in P* call pointer to. This operator can be used on most values, and it tells the value to create a new pointer which points to itself. Then we assign this new pointer with = to our my_pointer variable.

Now, my_pointer holds a pointer to my_value, and to convert it back, the pointer supports an operator with a lovely name: The indirection operator *. If you have the pointer, and wishes to use the value it's pointing to, you must always add the * in front of it, like we do here when we print the value out.

In the big example, the function move_to_neighbour takes two pointers to integers and modifies the values they point to. When we call the function, we add & in front of the variable names to convert them to pointers to match what the function expects to receive.

Now some object orientation!

The example we've just gone through doesn't take advantage of the object orientation in P*. It works since we've only keeping track of two values, phone- and house number, but what if there were 20 different values? We would need to have 20 arguments in our move_to_neighbour function, and at the same time keep track of 20 different variables in our main program block. To clean up mess like this, we create an object, a struct, to stash our stuff into. Read more about structs in the Structs-section of the documentation.

SCENE main {
	/* Define a struct wich can hold contact info for someone */
	struct contact_info {
		/* These variables are only visible inside this struct */
		int house_number;
		int phone_number;
		string street;

		/* The constructor which sets the initial values */
		contact_info (int a, int b, string c) {
			house_number = a;
			phone_number = b;
			street = c;
		};

		/* The function which modifies the values. No arguments! */
		public void move_to_neighbour() {
			house_number += 1;
			phone_number += 100;
		};

		/* Print out the values, still no arguments needed. */
		public void print_info() {
			echo "- Street is $street\n";
			echo "- House number $house_number\n";
			echo "- Phone number is $phone_number\n";
		};
	};

	/* Create an instance of the struct 'contact_info' and
	   call the constructor to set my information */
	contact_info my_info(134, 5550453, "Sesame Street");

	/* Call the print function of the struct, gives 134 and 5550453 */
	echo "This was my contact info before i moved:\n";
	my_info->print_info();

	/* Call the move function to move to our neighbour */
	my_info->move_to_neighbour();

	/* Tell the struct to print the values one more time,
	   it should give out 135 and 5550553 */
	echo "This is where i live now:\n";
	my_info->print_info();
}
		

But now the pointers are gone again! Does this mean that we don't need them after all? If we only hold the contact info for a single person, this example works pretty well. But what if there were five persons which were moving? Or ANY number of persons?

Let's first create an array to hold contact info for may persons, arrays should always be used for cases like this. Not that arrays are template types just as pointers, and we create and array here to hold values of the type contact_info

array<contact_info> neighbourhood_info;
		

Now, let's have some people moving to our neighbourhood:

/* The '@contact_info' returns the size of the array. The first time we
   use it, the size is zero, so we set the 0'th element to the person
   who lives in Blue Street. The next time the size is 1, because we
   just added a value, so the Polar Bear drive-person is added to this
   slot, and so on. */
neighbourhood_info[@neighbourhood_info](2, 5551000, "Blue street");
neighbourhood_info[@neighbourhood_info](34, 5552000, "Polar Bear drive");
neighbourhood_info[@neighbourhood_info](56, 5553000, "Mountain road");
neighbourhood_info[@neighbourhood_info](78, 5554000, "Apple close");
		

What happens now, is that ALL the four persons have to move to their neighbours due to a flood. To do an operation many times, we use a loop to iterate through the array. Here, all loops are possible to use, but the easiest way is to use a foreach loop with a pointer.

/* Declare a pointer to some value of the type 'contact_info' */
pointer<contact_info> iterator;

/* The foreach loop will run multiple times, and for each run, it will
   put a new value into our pointer called 'iterator' beginning with 
   the first one. */
foreach (iterator; neighbourhood_info) {
	/* Call the move function on a person. Remember the star!!! */
	*iterator->move_to_neighbour();
}
		

Now, all persons have moved. We didn't have to use a pointer in the foreach loop, but not without issues. Let's see the alternative first before we smash it to pieces:

/* Declare a temporary variable used in the foreach loop. The asterisk, *,
   tells P* that we shouldn't call any constructor for this variable,
   we don't need that since we'll be overwriting it in the loop anyway. */
contact_info *temp;

foreach (temp; neighbourhood_info) {
	temp->move_to_neighbour();
}
		

What happens here, is that for every iteration of the loop, a value from our neighbourhood array is COPIED into the temporary variable. When we then call the move function, the temporary value gets modified, but the original value stays unchanged, and the person doesn't move to it's neighbour.

The other advantage of using pointers when we pass structs around to functions, is that we prevent the struct from being copied. Copying structs in P* uses much more CPU and memory on your box than pointers. Pointers also use less memory than default types like int, but since copying an int is very fast, it's usually OK to keep the program readable and skip the pointers if we don't need them.

Let's put in the full example, with structs, arrays and pointers, for you to copy-paste and play with:

SCENE main {
	/* Define a struct wich can hold contact info for someone */
	struct contact_info {
		int house_number;
		int phone_number;
		string street;

		/* Constructor for this struct, set's all the values */
		contact_info (int a, int b, string c) {
			house_number = a;
			phone_number = b;
			street = c;
		};

		/* Function to move to the neighbours */
		public void move_to_neighbour() {
			house_number += 1;
			phone_number += 100;
		};

		/* Print out the content of this struct in a readable form */
		public void print_info() {
			echo "- Person lives in $street number $house_number with tel. $phone_number\n";
		};
	};

	/* Declare an array to hold contact info of many persons */
	array<contact_info> neighbourhood_info;

	/* Construct contact info for four persons and put this into an array */
	neighbourhood_info[@neighbourhood_info](2, 5551000, "Blue street");
	neighbourhood_info[@neighbourhood_info](34, 5552000, "Polar Bear drive");
	neighbourhood_info[@neighbourhood_info](56, 5553000, "Mountain road");
	neighbourhood_info[@neighbourhood_info](78, 5554000, "Apple close");

	/* Declare a temporary variable used in the foreach loop */
	contact_info *temp;

	foreach (temp; neighbourhood_info) {
		temp->move_to_neighbour();
	}

	/* Let's verify that no move really happened, since we used a temporary variable. */
	echo "Result after attempting to move using a temporary variable, no change has happened:\n";
	foreach (temp; neighbourhood_info) {
		temp->print_info();
	}

	/* Do the moving the proper way, now with a pointer */
	pointer<contact_info> iterator;

	/* The foreach loop will run multiple times, and for each run, it will
	   put a new value into our pointer called 'iterator' beginning with
	   the first one. */
	foreach (iterator; neighbourhood_info) {
		/* Call the move function on a person. Remember the star!!! */
		*iterator->move_to_neighbour();
	}

	/* Make sure that the moving occured. */
	echo "\nResult after moving using pointer:\n";
	foreach (iterator; neighbourhood_info) {
		*iterator->print_info();
	}
}