Vala Tutorial
Contents
Introduction
Disclaimer: Vala is an ongoing project, and its features may change. I will try to keep this tutorial as up to date as I can, but I'm not perfect. Also, I can't promise that the techniques which I suggest are necessarily the best in practice, but again I will try to keep up with that sort of thing.
What is Vala?
Vala is a new programming language that allows modern programming techniques to be used to write applications that run on the GNOME runtime libraries. In particular, glib and gobject. This platform has long provided a very complete programming environment, with such features as a dynamic type system and assisted memory management. Before Vala, the only ways to program for the platform were with the machine native C API, which exposes a lot of often unwanted detail. With a high level language that has an attendant virtual machine, such as Python or the Mono language. Or alternatively, with C++ through a wrapper library.
Vala is different from all these other techniques, as it outputs C code which can be compiled to run with no extra library support beyond the GNOME platform. This has several consequences, but most importantly:
- Programs written in Vala should have broadly similar performance to those written directly in C, whilst being easier and faster to write and maintain.
- A Vala application can do nothing that a C equivalent cannot. Whilst Vala introduces a lot of language features that are not available in C, these are all mapped to C constructs, although they are often ones that are difficult or too time consuming to write directly.
As such, whilst Vala is a modern language with all of the features you would expect, it gains its power from an existing platform, and must in some ways comply with the rules set down by it.
Who is this tutorial for?
This tutorial will not go into depth about basic programming practices. It will only briefly explain the principles of Object Oriented programming, instead focusing on how Vala applies the concepts. As such it will be helpful if you have experience of a variety of programming languages already, although in depth knowledge of any particular one is not required.
Vala shares a lot of syntax with C#, but I will try to avoid describing features in terms of their similarity or differences with either C# or Java, with the aim of making the tutorial more accessible.
What will be useful is a reasonable understanding of C. Whilst this isn't needed for understanding Vala per se, it is important to realise that Vala programs are executed as C, and will often interact with C libraries. Knowledge of C will certainly make a deeper understanding of Vala far easier to come by.
Conventions
Code will be in monospaced text, commands will all be prefaced with a $ prompt. Other than that, everything should be obvious. I tend to code very explicitly, including some information that is actually implied. I will try to explain where some things can be omitted, but that doesn't mean that I encourage you do to this.
At some point I will add in references to the Vala documentation, but that isn't really possible yet.
A First Program
Sadly predictable, but still:
using GLib; public class Test.HelloObject : GLib.Object { public static void main(string[] args) { stdout.printf("Hello, World"); } }
Of course, that is Vala's Hello World program. I expect you can recognise some parts of it well enough, but just to be thorough I shall go through it step by step.
using GLib;
A using line informs the compiler that this file is going to be referring to things in the namespace given, and therefore allows them to be used without giving their fully qualified name. The GLib namespace is imported by default, so this line is optional.
public class Test.HelloObject : GLib.Object {
This line identifies the beginning of a class definition. Classes in Vala are very similar in concept to other languages. A class is basically a type of object, of which instances can be created, all having the same properties. The implementation of classed types is taken care of by the gobject library, but details of this are not important for general usage.
What is important to note is that this class is specifically described as being a subclass of GLib.Object. This is because Vala allows other types of class, but in most cases, this is the sort that you want. In fact, some language features of Vala are only allowed if your class is descended from GLib's Object.
Other parts of this line show namespacing and fully qualified names, although these will be explained later.
public static void main(string[] args) {
This is the start of a method definition. A method is a function related to a type of object that can be executed on an object of that type. The static method means that the method can be called without possessing a particular instance of the type. The fact that this method is called main and has the signature it does means that Vala will recognise it as the entry point for the program.
stdout.printf("Hello, World");
stdout is an object in the GLib namespace that Vala ensure you have access to whenever required. This line instructs Vala to execute the method called printf of the stdout object, with the hello string as an argument. In Vala, this is always the syntax you use to execute a method on an object, or to access an object's data.
The last lines simply end the definitions of the method and class.
To Run
Assuming you have Vala installed, then all it takes to compile and execute this program is:
$ valac -o hello hello.vala $ ./hello
valac is the Vala compiler, which will convert your Vala code into C. It can also automate the whole process of compiling the C into machine code, which is what supplying the -o switch requests. The resulting binary can then be directly executed on the machine. You can probably guess the output.
If you give valac the -C switch, it will also create two files called hello.h and hello.c. If you look at the content of these files you can see that programming a class in Vala is equivalent to the same task in C, but a whole lot more succinct. You will also notice that this class is defined dynamically in the running system. This is a good example of the power of the GNOME platform, but as I've said before, you do not need to know much about this to use Vala.
Basic Concepts
Files
Vala code is written in files with .vala extensions. Vala does not enforce as much structure as a language like Java - there are not concepts of packages or class files in the same way. Instead structure is defined by text inside each file, describing the logical location of the code with constructs such as namespaces. When you want to compile Vala code, you give the compiler a list of the files required, and Vala will work out how they fit together.
The upshot of all this is that you can put as many classes or functions into a file as you want, even combining parts of different namespaces in together. This is not a good idea.
There are certain conventions you probably want to follow. A good example of how to structure a project in Vala is the Vala project itself.
Data Types
Broadly speaking there are two types of data in Vala: Reference types and Value types. These names describe how instances of the types are passed around the system - a value type is copied whenever it is assigned to a new identifier, a reference type is not copied, instead the new identifier is simply a new reference to the same object.
Vala's value types are the basic simple data types, and the compound type "struct". The simple types are:
- char, uchar
- int, uint
- float, double
- bool (boolean)
- unichar (Unicode character)
The reference types are all types declared as a class, regardless of whether they are descended from GLib's Object. Vala will ensure that when you pass an object by reference the system will keep track of the number of references currently alive in order to manage memory for you. If you define your own classes as being descending from GLib's Object, your types will do this as well. Also all arrays are created as reference types, as is the string type, representing a UTF-8 encoded string.
Syntax
Vala's syntax is an amalgam heavily based on C#'s. As a result, most of this will be familiar to programmers who know any C-like language, and in light of this I have kept things brief.
Scope is defined using braces. An object or reference is only valid between { and }. These are also the delimiters used to define classes, functions, code blocks etc, so they automatically have their own scope. Vala is not strict about where variables are declared.
An identifier is defined by its type and a name, e.g. int c meaning an integer called c. In the case of value types this also creates an object of the given type. For reference types these just define a new reference that doesn't initially point to anything.
Reference types are instantiated using the new operator and the name of a construction method, which is usually just the name of the type, e.g. Object o = new Object() creates a new Object and makes o a reference to it.
An array is created by giving a type name followed by [] and assigning using the new operator e.g. int[] a = new int[10] to create an array of integers. The length of such an array can be obtained by the length member variable e.g. int count = a.length;. Note that if you write Object[] a = new Object[10] no Objects will be created, just the array to store them in.
Finally Vala has a mechanism called Type Inference, whereby a local variable may be defined using var instead of giving a type, so long as it is unambiguous what type is meant.
Operators:
=
assignment. The left operand must be an identifier, and the right must result in a value or reference as appropriate.
+, -, /, *, %
basic arithmetic, applied to left and right operands.
+=, -=, /=, *=, %=
arithmetic operation between left and right operands, where the left must be an identifier, to which the result is assigned.
++, --
increment and decrement operations with implicit assignment. These take just one argument, which must be an identifier of a simple data type. The value will be changed and assigned back to the identifier. These operators may be placed in either prefix or postfix positions - with the former the evaluated value of the statement will be the newly calculated value, with the latter the original value is returned.
|, ^, &, ~, |=, &=, ^=
bitwise operations: or, exclusive or, and, not. The second set include assignment and are analogous to the arithmetic versions. These can be applied to any of the simple value types. (There is of no assignment operator associated with ~ because this is a unary operator. The equivalent operation is just a = ~ a).
<<, >>
bit shift operations, shifting the left operand a number of bits according the right operand.
<<=, >>=
bit shift operations, shifting the left operand a number of bits according the right operand. The left operand must be an identifier, to which the result is assigned.
==
equality test. Evaluates to a bool value dependent on whether the left and right operands are equal. In the case of value types this means their values are equal, in reference types that the objects are the same instance. An exception to this rule is the string type, which is tested for equality by value.
<, >, >=, <=, !=
inequality tests. Evaluate to a bool value dependent on whether the left and right operands are different in the manner described. These are valid for simple value data types, and the string type.
!, &&, ||
logic operations: not, and, or. These operations can be applied to boolean values - the first taking just one value the others two.
Operators cannot be overloaded in Vala. There are extra operators that are valid in the context of lambda declarations and other specific tasks - these are explained in their context they are applicable.
Control structures:
while (a > b) { a--; }
will decrement "a" repeatedly, checking before each iteration that "a" is greater than "b".
do { a--; } while (a > b);
will decrement "a" repeatedly, checking after each iteration that "a" is greater than "b".
for (int a = 0; a < 10; a++) { stdout.printf("%d\n", a); }
will initialize "a" to 10, then print a repeatedly until "a" is no longer less than 10, incrementing a after each iteration.
foreach (int a in int_array) { stdout.printf("%d\n", a); }
will print out each integer in an array, or another iterable collection. The meaning of iterable will be described later.
All of the four preceding types of loop may be controlled with the keywords break and continue. A break instruction will cause the loop to immediately terminate, while continue will jump straight to the test part of the iteration.
if (a > 0) { stdout.printf("a is greater than 0") } else if (a < 0) { stdout.printf("a is less than 0") } else { stdout.printf("a is equal to 0") }
executes a particular piece of code based on a set of conditions. The first condition to match decides which code will execute, if a is greater than 0 it will not be tested whether it is less than 0. Any number of else if blocks is allowed, and zero or one else blocks.
switch (a) { case 1: stdout.printf("one"); break; case 2: stdout.printf("two"); break; default: stdout.printf("unknown"); break; }
a switch statement runs exactly one or zero sections of code based on the value passed to it. In Vala there is no fall through between cases, as soon as a match is found no more will be checked. In order to ensure this, each case must include a break, return or throw statement.
Prefixing symbols with @ let you using reserved keywords. This is mainly useful for bindings. You can use an argument @foreach for example.
Definitions:
int function_name(int arg1, Object arg2) throws ErrorType { return 1; }
this code defines a function, having the name "function_name", taking two arguments, one an integer and the other an Object (the first passed by value, the second as a reference as described).
The function will return an integer, which in this case is 1.
All Vala functions are C functions, and therefore take an arbitrary number of arguments and return one value. They may approximate more return values by placing data in locations known to the calling code. Details of how to do this are in the "Parameter Types" section in the advanced part of this tutorial.
This particular function definition says that the function might throw an exception - this may be omitted if no exceptions are possible. Details of error handling are included later in this tutorial.
delegate void DelegateType(int a);
a delegate is a type of function pointer, allowing chunks of code to be passed around like objects. The example above defines a delegate called "DelegateType" which represents a function taking an int and not returning a value. Any function with such a signature may be used as a "delegate_name" as shown in the following sample:
void f1(int a) { stdout.printf("%d\n", a); } ... void f2(DelegateType d, int a) { d(a); } ... f2(f1, 5);
this code will execute the function "f2", passing in a pointer to function "f1" and the number 5. "f2" will then execute the function "f1", passing it the number.
f2(a => { stdout.printf("%d\n", a); }, 5);
an inline function, also known as a lambda function, can be defined in Vala with the => operator.
In this example the function "f2", as defined above, is called with a reference to a unnamed function which takes one argument and prints it. Notice that neither parameter or return types are explicitly given. Instead the types are derived from the signature of the delegate that should be passed to "f2". This example is in fact functionally equivalent to the previous.
DelegateType d1 = f1; DelegateType d2 = (a) => { stdout.printf("%d\n", a); };
delegates may also be created locally, and functions assigned either with their names, or with an anonymous lambda definition.
NOTE: Until very recently, it wasn't possible to surround a single parameter by '(' and ')' due to a bug in flex. If you bump into a problem with this syntax, the solution is to omit the '(' and ')', so the above code would look like:
DelegateType d1 = f1; DelegateType d2 = a => { stdout.printf("%d\n", a); };
namespace NameSpaceName { ... }
everything between the braces in this statement is in the namespace "NameSpaceName" and must be referenced as such. Any code outside this namespace must be either used qualified names for anything within the name of the namespace, or be in a file with an appropriate using declaration. Namespaces may contain any code except using statements.
Namespaces can be nested, either by nesting one declaration inside another, or by giving a name of the form "NameSpace1.NameSpace2".
Several other types of definition can declare themselves to be inside a namespace by following the same naming convention, e.g. class NameSpace1.Test { ... }. Note than when doing this, the final namespace of the definition will be the one the declaration is nested in plus the namespaces declared in the definition.
struct StructName { public int a; }
defines a struct type, i.e. a compound value type. A Vala struct may have methods in a limited way, and therefore may have private data, meaning the explicit public descriptor is required.
class ClassName : SuperClassName, InterfaceName { }
defines a class, i.e. a reference type. There is much more syntax related to classes, which is discussed more fully in the next section.
interface InterfaceName : SuperInterfaceName { }
defines an interface, i.e. a non instantiable type. In order to create an instance of an interface you must first implement it in a method. The details of this are described in the next section.
Comments:
Vala allows comments in code in two different ways.
// Comment continues until end of line /* Comment lasts between delimiters */
These are handled in the same way as most other languages and so need little explanation.
Objects
Although Vala doesn't force you to work with objects, some features are not available any other way. As such, you will certainly want to program in an object-oriented style most of the time. As with most current languages, in order to define your own object types, you write a class definition.
A class definition states what data each object of its type has, what other object types it can hold references to, and what methods can be executed on it. The definition can include a name of another class which the new one should be a subclass of. An instance of a class is also an instance of all it's class's superclasses, as it inherits from them all their methods and data, although it may not be able to access all of this itself. A class may also implement any number of interfaces, which are sets of method definitions that must be implemented by the class - an instance of a class is also an instance of each interface implemented by its class or superclasses.
Classes in Vala may also have "static" members. This modifier allows either data or methods to be defined as belonging to the class as a whole, rather than to a specific instance of it. Such members can be accessed without possessing an instance of the class.
Basics
A simple class may be defined as follows:
public class TestClass : GLib.Object { public int first_data = 0; private int second_data; construct { this.second_data = 5; } public int function_1() { GLib.stdout.printf("private data: %d", this.second_data); return this.second_data; } }
This code will define a new type (which is registered automatically with the gobject library's dynamic type system) that contains 3 members. There are two data members, the integers defined at the top, and one method called "function_1", which returns an integer. The class declaration states that this class is a subclass of GLib.Object, and therefore instances of it are also Objects, and contain all the members of that type also. The fact that this class is descended from Object also means that there are special features of vala that can be used to easily access some of Object's features.
This class is described as public. The implication of this is that it can referenced directly by code outside of this file - if you are a C programmer of glib/gobject, you will recognize this as being equivalent to defining the class interfaces in a header file that other code can include.
The members are also all described as either public or private. The member first_data is public, so it is visible directly to any user of the class, and can be modified without the containing instance being aware of it. The second data member is private, and so can only be referenced by code belonging to this class.
The code block beginning construct is approximately equivalent to a constructor in Java or C#, except of course that it takes no arguments. This is as a result of how the gobject library works, and does not mean that an object cannot be created with data. The way of achieving this involves properties, and will be explained in the "construction" section of this document, after properties have been introduced.
The final part of this class is a method definition. This method is to be called "function_1", and it will return an integer. As this method is not static, it can only be executed on an instance of this class, and may therefore access members of that instance. It can do this through the this reference, which always points to the instance the method is being called on. Unless there is an ambiguity, the this identifier can be omitted if wished.
You can use this new class as follows:
TestClass t = new TestClass(); t.first_data = 5; t.function_1();
Properties
Any class that is descended from GLib's Object class inherits, amongst other features, access to a system of properties. These are like data members of a class, but can be more closely controlled by providing code to handle access. These should be familiar to C# programmers, and recognizable to programmers of Java-like languages as being a different approach to hand written getX and setX methods.
What good reasons are there for actually using properties?
Within a class, properties can be defined in various ways, including:
public int prop_1 { get; set; } private int _prop_2; public int prop_2 { get { return this._prop_2; } set { this._prop_2 = value; } } [Description(nick="3rd property", blurb="This is the 3rd property which default to 42")] public int prop_3 { get; private set; default = 42; }
This code fragment defines three properties. The first allows Vala to make all decisions about how to manage the data, while the second specifically defines the data to be used, and also writes custom get and set methods. In the third case, the setter is defined 'private', and so only accessible to methods of the same class, and the default value of 42 is assigned. In all cases the properties are defined as representing integers.
In order to use these properties, refer to them in the same way as public members of the class, i.e. (assuming a class called "T"):
T t = new T(); stdout.printf("%d\n", t.prop_1); t.prop_1 = 5; stdout.printf("%d\n", t.prop_1);
The get and set parts of these properties are independent, and can be omitted or redefined as needed. You could in fact change the get method of prop_2 to return any data at all, as long as it is an integer.
If the get and set methods are declared but not defined with code blocks, as in "prop_1", the value of the variable will automatically point to a class member, "_" + "var", i.e. "prop_1" points to "_prop_1". As a general rule though, you should not need to be aware of this, and it should not be relied upon.
There is a third method of a property, called construct. A property with this method can have a value assigned to it before the object's constructor is called. The syntax for this is the same as for get and set, only using the construct keyword instead. Use of this property type is explained in the next section.
Construction
Any class descended from GLib's Object may have a method called construct. This is guaranteed to be called when an instance of this class is created, even if it is created as a subtype. This code block may not take any parameters or return a value, but is otherwise simply a method of class. This means it can call other functions and set member variables as needed.
When a class needs to accept data before it can construct itself, it needs to use construction properties. These properties may be set before the construct block is called to provide initial information. There are in fact several ways to achieve this, of which the basic ones are described. The common steps however are to provide an interface to pass the data, as follows:
public class T : GLib.Object { public int init_opt { get; construct; } public T(int init_opt) { this.init_opt = init_opt; } }
This will require that when a user tries to create a new T, they will have to pass an integer. The method here that looks like a constructor in other languages in fact is entirely specialised to setting properties, and can do nothing else.
Instances of this type will now look like:
T t = new T(5);
Now that construction properties are introduced, we can look at the process of instantiating an object derived from GLib's Object type.
The user applies the new operator to a type, e.g. new T(5);
- The system will find the associated constructor that takes one integer and set all construction properties as it requests.
The system will run the construct blocks for each type in the hierarchy from Object up to T, in turn.
- Any non construct properties in the constructor will be set.
The choice in the process occurs in deciding where to place code that acts on construction properties. Normal procedure is for a class to do no particular initialisation until all superclasses have done theirs. This is what happens when all such code is in the construct block. However, construction properties are defined just as get and set properties, and therefore can run arbitrary code on assignment. If you need to do initialisation based on a single construction property, it is possible to write a custom construct method, which will be executed immediately on assignment, and before any other construction code, at number 2 in the above list.
Destruction
Although Vala manages the memory for you, you might need to add your own destructor to deal with pointers, or un-managed memory. The syntax is the same as C# and C++:
public class T : GLib.Object { ~T () { message ("in dtor"); } }
Note: the syntax might change in future versions (> 0.2.0).
Signals
Signals are a system provided by the Object class in GLib, and made easily accessible by Vala to all descendants of Object. A signal is recognisable to C# programmers as an event, or to Java programmers as an alternative way of implementing event listeners. In short, a signal is simply a way of executing an arbitrary number of externally identically function (i.e. ones with the same signature) at approximately the same time. The actual methods of execution are internal to gobject, and not important to Vala programs.
A signal is defined as a member of a class, and appears similar to a method with no body. Signal handlers can then be added to the signal using the special += operator. In order to dive right in at the deep end, the following example also introduces lambda functions, a very useful way to write signal handling code in Vala:
public class Test : GLib.Object { public signal void sig_1(int a); public static void main(string[] args) { Test t1 = new Test(); t1.sig_1 += (t, a) => { stdout.printf("%d\n", a); }; t1.sig_1(5); } }
This code introduces a new class called "Test", using familiar syntax. The first member of this class is a signal, called "sig_1", which is defined as passing an integer. In the main function of this program, we first create a Test instance - a requirement since signals always belong to instances of classes. Next, we assign to our instance's "sig_1" signal a handler, which we define inline as a lambda function. The definition states that the function will receive two arguments which we call "t" and "a", but do not provide types for. We can be this terse because Vala already knows the definition of the signal and can therefore understand what types are required.
The reason there are two arguments to the handler is that whenever a signal is emitted, the object on which it is emitted is passed as the first argument to the handler. The second argument is that one that the signal says it will provide.
Finally, we get impatient and decide to emit a signal. We do this by calling the signal as though it was a method of our class, and allow gobject to take care of forwarding the message to all attached handlers. Understanding the mechanism used for this is not required to use signals from Vala.
NB: Currently the public access modifier is the only possible option - all signals can be both connected to and emitted by any piece of code.
Inheritance
In Vala, a class may derive from one or zero other classes. In practice this is always likely to be one, as there is no implicit inheritance as there is in languages like Java.
When defining a class that inherits from another, you create a relationship between the classes where instances of the subclass are also instances of the superclass. This means that operations on instances of the superclass are also applicable on instances of the subclass. As such, wherever an instance of the superclass is required, an instance of the subclass can be substituted.
When writing the definition of a class it is possible to exercise precise control over who can access what methods and data in the object. The following example demonstrates a range of these options:
class SuperClass : GLib.Object { private int private_data; protected void protected_function() { } public static void public_static_function() { } } class SubClass : SuperClass { }
private_data is an instance data member of "SuperClass". There will be a member of this type in every instance of "SuperClass", and it is declared private so will only be accessible by code that is a part of "SuperClass".
protected_function is an instance method of "SuperClass". You will be able to execute this method only an instance of "SuperClass" or of one of its subclasses, and only from code that belongs to "SuperClass" or one of its subclasses - this latter rule being the result of the protected modifier.
public_static_function has two modifiers. The static modifier means that this method may be called without owning an instance of "SuperClass" or of one of its subclasses. As a result, this method will not have access to a this reference when it is executed. The public modifier means that this method can be called from any code, no matter its relationship with "SuperClass" or its subclasses.
Given these definitions, an instance of "SubClass" will contain all three members of "SuperClass", but will only be able to access the non-private members. External code will only be able to access the public method.
There is another modifier for methods, called abstract. This modifier allows you to describe a method that is not actually implemented in the class. Instead, it must be implemented by subclasses before it can be called. This allows you to define operations that can be called on all instances of a type, whilst ensuring that all more specific types provide their own version of the functionality.
It is also possible to declare an entire class as abstract. The result of this is to prevent any instantiation of the type. This is similarly useful to defining abstract methods, in that you can describe features of a general type, but only allow more specific type (subclasses) to actually be instantiated.
Interfaces
A class in Vala may implement any number of interfaces. Each interface is a type, much like a class, but one that cannot be instantiated. By "implementing" one or more interfaces, a class may declare that its instances are also instances of the interface, and therefore may be used in any situation where an instance of that interface is expected.
The procedure for implementing an interface is the same as for inheriting from classes with abstract methods in - if the class is to be useful it must provide implementations for all methods that are described but not yet implemented.
A simple interface definition looks like:
public interface ITest : GLib.Object { public abstract int data_1 { get; set; } public abstract void function_1(); }
This code describes an interface "ITest" which requires GLib.Object as parent and contains two members. "data_1" is a property, as described above, except that it is declared abstract. Vala will therefore not implement this property, but instead require that classes implementing this interface have a property called "data_1" that has both get and set accessors - it is required that this be abstract as an interface may not have any data members. The second member "function_1" is a method. Here it is declared that this method must be implemented by classes that implement this interface, however gobject is unusual in that it actually allows method implementations in interfaces, hence the requirement that this one be declared abstract.
The simplest possible full implementation of this interface is:
public class Test1 : GLib.Object, ITest { public int data_1 { get; set; } public void function_1 () { } }
And may be used as follows:
var t = new Test1 (); t.function_1 (); ITest i = t; i.function_1 ();
Interfaces in Vala may not inherit from other interfaces, but they may declare other interfaces to be prerequistes, which works in roughly the same way. For example, it may be desirable to say that any class that implements a "List" interface must also implement a "Collection" interface. The syntax for this is exactly the same as for describing interface implementation in classes:
public interface List : Collection { }
This definition of "List" may not be implemented in a class without "Collection" also being implemented, and so Vala enforces the following style of declaration for a class wishing to implement "List", where all implemented interfaces must be described:
public class ListCLass : GLib.Object, Collection, List { }
Vala interfaces may also have a class as a prerequisite. If a class name is given in the list of prerequisites, the interface may only be implemented in classes that derive from that prerequisite class. This is often used to ensure that an instance of an interface is also a GLib.Object subclass, and so the interface can be used, for example, as the type of a property.
The fact that interfaces can not inherit from other interfaces is mostly only a technical distinction - in practice Vala's system works the same as other languages in this area, but with the extra feature of prerequsite classes.
Polymorphism
"Polymorphism" describes the way in which the same object can be used as though it were more than one distinct type of thing. Several of the techniques already described here suggest how this is possible in Vala: An instance of a class may be used as in instance of a superclass, or of any implemented interfaces, without any knowledge of its actual type.
A logical extension of this power is to allow a subtype to behave differently to its parent type when addressed in exactly the same way. This is not a very easy concept to explain, so I'll begin with an example of what will happen if you do not directly aim for this goal:
class SuperClass : GLib.Object { public int function_1() { stdout.printf("SuperClass.function_1()\n"); } } class SubClass : SuperClass { public int function_1() { stdout.printf("SuperClass.function_2()\n"); } }
These two classes both implement a function called "function_1", and "SubClass" therefore contains two functions called "function_1", as it inherits one from "SuperClass". Each of these may be called as the following code shows:
SubClass o1 = new SubClass(); o1.function_1(); SuperClass o2 = o1; o2.function_1();
This will actually result in two different functions being called. The second line believes "o1" to be a "SubClass" and will call that class's version of the function. The fourth line believes "o2" to be a "SuperClass" and will call that class's version of the function.
The problem this example exposes, is that any code holding a reference to "SuperClass" will call the functions actually described in that class, even in the actual object is of a subclass. The way to change this behaviour is using virtual functions. Consider the following rewritten version of the last example:
class SuperClass : GLib.Object { public virtual int function_1() { stdout.printf("SuperClass.function_1()\n"); } } class SubClass : SuperClass { public override int function_1() { stdout.printf("SuperClass.function_2()\n"); } }
When this code is used in the same way as before, "SubClass"'s "function_1" will be called twice. This is because we have told the system that "function_1" is a virtual function, meaning that if it is overridden in a subclass, that new version will always be executed on instances of that subclass, regardless of the knowledge of the caller.
This distinction is probably familiar to programmers of some languages, such as C++, but it is in fact the opposite of Java style languages, in which steps must be taken to prevent a method being virtual.
You will probably now also have recognised that when method is declared as abstract it must also be virtual. Otherwise, it would not be possible to execute that function given an apparent instance of the type it was declared in. When implementing an abstract function in a subclass, you may therefore choose to declare the implementation as override, thus passing on the virtual nature of the method, and allowing subtypes to do the same if they desire.
It's also possible to implement interface methods in such a way that subclasses can change the implementation. The process in this case is for the initial implementation to declare the function implementation to be virtual, and then subclasses can override as required.
When writing a class, it is common to want to use functionality defined in a class you have inherited from. This is complicated where the function name is used more than one in the inheritance tree for your class. For this Vala provides the base keyword. The most common case is where you have overridden a virtual method to provide extra functionality, but still need the parent class' method to be called. The following example shows this case:
public override void function_name() { base.function_name(); extra_task(); }
Advanced Features
Error Handling
GLib has a system for managing runtime exceptions called GError. Vala translates this into a form familiar to modern programming languages, but its implementation means it is not quite the same as in Java or C#. It is important to consider when to use this type of error handling - GError is very specifically designed to deal with recoverable runtime errors, i.e. factors that are not known until the program is run on a live system, and that are not fatal to the execution. You should not use GError for problems that can be foreseen, such as reporting that an invalid value has been passed to a function. If a function, for example, requires a number greater than 0 as a parameter, it should fail on negative values using the debugging utils such as assert.
Using exceptions is a matter of:
1) Declaring that a function may raise an error:
void function_1(int a) throws IOError { ... }
2) Throwing the error when appropriate:
if (!check_file()) { throw new IOError.FILE_NOT_FOUND("Requested file could not be found."); }
3) Catching the error from the calling code:
try { function_1(-1); } catch (IOError ex) { stdout.printf("Error: %s\n", ex.message); }
All this appears more or less as in other languages, but defining the types of errors allowed is fairly unique. Errors have three components, known as "domain", "code" and message. Messages we have already seen, it is simply a piece of text provided when the error is created. Error domains describe the type of problem, and equates to a subclass of "Exception" in Java or similar. In the above examples we imagined an error domain called "IOError". The third part, the error code is a refinement describing the exact variety of problem encountered. Each error domain has one or more error codes - in the example there is a code called "FILE_NOT_FOUND".
The way to define this information about error types is related to the implementation in glib. In order for the examples here to work, a definition is needed such as:
errordomain IOError { FILE_NOT_FOUND }
When catching an error, you give the error domain you wish to catch errors in, and if an error in that domain is thrown, the code in the handler is run with the error assigned to the supplied name. From that error object you can extract the error code and message as needed. If you want to catch errors from more than one domain, simply provide extra catch blocks. There is also an optional block that can be placed after a try and any catch blocks, called finally. This code is to be run always at the end of the section, regardless of whether an error was thrown or any catch blocks were executed, even if the error was in fact no handled and will be thrown again. This allows, for example, any resources reserved in the try block be freed regardless of any errors raised. A complete example of these features:
errordomain ErrorType1 { CODE_1A } errordomain ErrorType2 { CODE_2A } public class Test : GLib.Object { public static void thrower() throws ErrorType1, ErrorType2 { throw new ErrorType1.CODE_1A("Error"); } public static void catcher() throws ErrorType2 { try { thrower(); } catch (ErrorType1 ex) { // Deal with ErrorType1 } finally { // Tidy up } } public static void main(string[] args) { try { catcher(); } catch (ErrorType2 ex) { // Deal with ErrorType2 } } }
This example has two error domains, both of which can be thrown by the "thrower" function. Catcher can only throw the second type of error, and so must handle the first type if "thrower" throws it. Finally the "main" function will handle any errors from "catcher".
Attributes
Attributes
Debugging
assert etc.
Generics
Vala includes a runtime generics system, by which a particular instance of a class can be restricted with a particular type or set of types chosen at construction time. This restriction is generally used to require that data stored in the object must be of a particular type, for example in order to implement a list of objects of a certain type. In that example, Vala would make sure that only objects of the requested type could be added to the list, and that on retrieval all objects would be cast to that type.
In Vala, generics are handled while the program is running. When you define a class that can be restricted by a type, there still exists only one class, with each instance customised individually. This is in contrast to C++ which creates a new class for each type restriction required - Vala's is similar to the system used by Java. This has various consequences, most importantly: that static members are shared by the type as a whole, regardless of the restrictions placed on each instance; and that given a class and a subclass, a generic refined by the subclass can be used as a generic refined by the class.
The following code demonstrates how to use the generics system to define a minimal wrapper class:
public class Wrapper<G> : GLib.Object { private G _data; public void set_data(G data) { this._data = data; } public G get_data() { return this._data; } }
This "Wrapper" class must be restricted with a type in order to instantiate it - in this case the type will be identified as "G", and so instances of this class will store one object of "G" type, and have functions to set or get that object. (The reason for this specific example is to provide reason explain that currently a generic class cannot use properties of its restriction type, and so this class has simple get and set methods instead.)
In order to instantiate this class, a type must be chosen, for example the built in string type (in Vala there is no restriction on what type may be used in a generic). To create an briefly use this class:
var wrapper = new Wrapper<string>(); wrapper.set_data("test"); var data = test.get_data();
As you can see, when the data is retrieved from the wrapper, it is assigned to an identifier with no explicit type. This is possible because Vala knows what sort of objects are in each wrapper instance, and therefore can do this work for you.
The fact that Vala does not create multiple classes out of your generic definition means that you can code as follows:
class TestClass : GLib.Object { } void accept_object_wrapper(Wrapper<Glib.Object> w) { } ... var test_wrapper = new Wrapper<TestClass>(); accept_object_wrapper(test_wrapper); ...
Since all "TestClass" instances are also Objects, the "accept_object_wrapper" function will happily accept the object it is passed, and treat its wrapped object as though it was a GLib.Object instance.
libgee
Gee is a library of collection classes, written in Vala. The classes should all be familiar to users of libraries such as Java's Foundation Classes. Gee consists of a set of interfaces and various types that implement these in different ways. Although Gee is not a part of Vala as such, it is used internally in Vala, and is sufficiently closely related that Vala supports certain special syntax for Gee related operations.
If you want to use libgee in your own application, don't use the internal copy included in vala, instead install the library separately on your system. Gee can be obtained from http://live.gnome.org/Libgee.
The fundamental types of collection are:
- Lists: Ordered collections of items, accesible by numeric index.
- Sets: Unordered collections of distinct.
- Maps: Unordered collection of items, accesible by index of arbitrary type.
All the lists and sets in the library implement the "Collection" interface, and all maps the "Map" interface. Lists also implement "List" and sets "Set". These common interfaces means not only that all collections of a similar type can be used interchangeably, but also that new collections can be written using the same interfaces, and therefore used with existing code.
Also common to every "Collection" type is the "Iterable" interface. This means that any object in this category can be iterated through using a standard set of functions, or directly in Vala using the foreach syntax.
All classes and interfaces use the Generics system. This means that they must be instantiated with a particular type of set of types that they will contain. The system will ensure that only the intended types can be put into the collections, and that when objects are retrieved they are returned as the correct type.
The Gee classes are:
ArrayList<G>
Implementing: Gee.Iterable<G>, Gee.Collection<G>, Gee.List<G>
An ordered list of items of type G backed by a dynamically resizing array. This type is very fast for accessing data, but potentially slow at inserting items anywhere other than at the end, or at inserting items when the internal array is full.
HashMap<K,V>
Implementing: Gee.Map<K,V>
A 1:1 map from elements of type K to elements of type V. The mapping is made by computing a hash value for each key - this can be customised by providing pointers to function for hashing and testing equality of keys in specific ways.
For example, if the keys are to be the built in string type, the map could be created using functions from Glib, as:
var map = new Gee.HashMap<string, Object>(GLib.str_hash, GLib.str_equal);
HashSet<G>
Implementing: Gee.Iterable<G>, Gee.Collection<G>, Gee.Set<G>
A set of elements of type G. Duplicates are detected by computing a hash value for each key - this can be customised by providing pointers to function for hashing and testing equality of keys in specific ways.
ReadOnlyCollection<G>
Implementing: Gee.Iterable<G>, Gee.Collection<G>
A wrapper for another collection with elements of type G. The wrapper has the same interface as its contained collection, but will not allow any form of modification, or any access to the contained collection.
ReadOnlyList<G>
Implementing: Gee.Iterable<G>, Gee.Collection<G>, Gee.List<G>
A wrapper for another list with elements of type G. The wrapper has the same interface as its contained list, but will not allow any form of modification, or any access to the contained list. This class behaves the same as "ReadOnlyCollection", except that it also implements the "List" interface.
ReadOnlyMap<K,V>
Implementing: Gee.Map<K,V>
A wrapper for another map of key type K and value type G. The wrapper has the same interface as its contained map, but will not allow any form of modification, or any access to the contained map.
ReadOnlySet<G>
Implementing: Gee.Iterable<G>, Gee.Collection<G>, Gee.Set<G>
A wrapper for another set of elements of type G. The wrapper has the same interface as its contained map, but will not allow any form of modification, or any access to the contained set.
Multi-Threading
A program written in Vala may have more that one thread of execution, allowing it it do more than one thing at a time. Exactly how this is managed is outside of Vala's scope - threads may be sharing a single processor core or not, depending on the environment.
A thread in Vala is not defined at compile time, instead it is simply a portion of Vala code that is requested at runtime to be executed as a new thread. This is done using the static functions of the Thread class in GLib, as shown in the following (very simplified) example:
using GLib; public class Threading : GLib.Object { public static void* thread_func() { stdout.printf("Thread running.\n"); return null; } public static void main(string[] args) { if (!Thread.supported()) { stderr.printf("Cannot run without threads.\n"); return; } try { Thread.create(thread_func, false); } catch (ThreadError ex) { return; } } }
This short program will request a new thread be created an executed. The code to be run being that in "thread_func". Also note the test at the start of the main function - a Vala program will not be able to use threads unless compiled appropriately, so it you build this example in the usual way, it will just display an error and stop running. Being able to check for thread support at runtime allows a program to be built to run either with or without threads if that is wanted. In order to build with thread support, run:
$ valac --thread -o thread-test threading.vala
This will both include required libraries and make sure the threading system is initialized whenever possible.
The program will now run without errors, but it will still not act as expected. Without any sort of event loop, a Vala program will terminate when its primary thread (the one created to run "main") ends. In order to control this behaviour, you can allow threads to cooperate. This can be done powerfully using event loops and asynchronous queues, but in this introduction to threading we will just show the basic capabilities of threads.
It is possible for a thread to tell the system that it currently has no need to execute, and thereby suggest that another thread should be run instead, this is done using the static method Thread.yield(). If this statement were placed at the end of the above "main" function, the runtime system will pause the main thread for an instant and check if there are other threads that can be run - on finding the newly created thread in a runnable state, it will run that instead until it is finished - and the program will act is it appears it should. However, there is no guarantee that this will happen still. The system is able to decide when threads run, and as such might not allow the new thread to finish before the primary thread is restarted and the program ends.
In order to wait for a thread to finish entirely there is the static function Thread.join(). Calling this with a Thread as its argument causes the calling thread to wait for the other thread to finish before proceeding. It also allows a thread to receive the return value of another, if that is useful. To implement joining threads:
weak Thread thread_1; try { thread_1 = Thread.create(thread_func, true); } catch (ThreadError ex) { return; } thread_1.join();
This time, when we create the thread we give true as the last argument. This marks the thread as "joinable". We also remember the value returned from the creation - a weak reference to a Thread object (weak references are explained later and are not vital to this section.) With this reference it is possible to join the new thread to the primary thread. With this version of the program it is guaranteed that the newly created thread will be allowed to fully execute before the primary thread continues and the program terminates.
All these examples have a potential problem, in that the newly created thread doesn't know the context in which it should run. In C you would supply the thread creation function with some data, in Vala instead you would normally pass an instance method to Thread.create, instead of a static method.
Resource Control
Whenever more than one thread of execution is running at once, there is a chance that data will need to be accessed simultaneously. This can lead to race conditions, where the outcome depends on when the system decides to switch between threads.
In order to control this situation, you can use the lock keyword to ensure that certain blocks of code will not be interrupted by other threads that need to access the same data. The best way to show this is probably with an example:
public class Test : GLib.Object { private int a { get; set; } public void action_1() { lock (a) { int tmp = a; tmp++; a = tmp; } } public void action_2() { lock (a) { int tmp = a; tmp--; a = tmp; } } }
This class defines two functions, where both need to change the value of "a". If there were no lock statements here, it would be possible for the instructions in these functions to be interweaved, and the resulting change to "a" would be effectively random. As there are the lock statements here, Vala will guarantee that if one thread has locked "a", another thread that needs the same lock will have to wait its turn.
In Vala it is only possible to lock members of the object that is executing the code. This might appear to be a major restriction, but in fact the standard use for this technique should involve classes that are individually responsible for controlling a resource, and so all locking will indeed be internal to the class. For example, in this example all access to "a" is contained in this one class.
Event Loops
GLib includes a system for running an event loop, in the classes around MainLoop. The purpose of this system is to allow you to write a program that waits for events and responds to them, instead of having to constantly check conditions. This is the model that GTK+ uses, so that a program can wait for user interaction without having to have any currently running code.
The following program creates and starts a MainLoop, and then attaches a source of events to it. In this case the source is a simple timer, that will execute the given function after 2000ms. The function will in fact just stop the main loop, which will in this case exit the program.
public class Test : GLib.Object { public static GLib.MainLoop loop; public static void main(string[] args) { loop = new GLib.MainLoop(null, false); var time = new GLib.TimeoutSource(2000); time.set_callback( () => { GLib.stdout.printf("Time!\n"); loop.quit(); return false; }, null ); time.attach(loop.get_context()); loop.run(); } }
When using GTK+, a main loop will be created automatically, and will be started when you call the `Gtk.main()' function. This marks the point where the program is ready to run and start accepting events from the user or elsewhere. The code in GTK+ is equivalent to the short example above, and so you may add event sources in much the same way, although of course you need to use the GTK+ functions to get a reference to the loop.
A common requirement in GUI programs is to execute some code as soon as possible, but only when it will not disturb the user. For this, you use IdleSource instances. These send events to the programs main loop, but request they only be dealt with when there is nothing more important to do.
For more information about event loops, see the GLib and GTK+ documentation.
Modules
Modules
ref and out Arguments
A function in Vala is passed zero or more parameters. The default behaviour when a function is called is as follows:
- Any value type parameters are copied to a location local to the function as it executes.
- Any reference type parameters are not copied, instead just a reference to them is passed to the function.
This behaviour can be changed with the modifiers 'ref' and 'out'.
- 'out' from the caller side
- you may pass an uninitialized variable to the method and you may expect it to be initialized after the method returns
- 'out' from callee side
- the parameter is considered uninitialized and you have to initialize it
- 'ref' from caller side
- the variable you're passing to the method has to be initialized and it may be changed or not by the method
- 'ref' from callee side
- the parameter is considered initialized and you may change it or not
void function_1(int a, out int b, ref int c) { ... } void function_2(Object o, out Object p, ref Object q) { ... }
These functions can be called as follows:
int a, b, c; a = 1; b = 2; c = 3; function_1(a, out b, ref c); Object o, p, q; o = new Object(); p = new Object(); q = new Object(); function_2(o, out p, ref q);
The treatment of each variable will be:
- "a" is of a value type. The value will be copied into a new memory location local to the function, and so changes to it will not be visible to the caller.
"b" is also of a value type, but passed as an out parameter. In this case, the value is not copied, instead a pointer to the data is passed to the function, and so any change to the function parameter will be visible to the calling code.
- "c" is treated in the same way as "b", the only change is in the signalled intent of the function.
- "o" is of a reference type. The function is passed a reference to the same object as the caller has. The function can therefore change that object, but if it reassigns to the parameter, that change will not be visible to the caller.
"p" is of the same type, but passed as an out parameter. This means that the function will receive a pointer to the reference to the object. It may therefore replace the reference with a reference to another object, and when the function returns the caller will instead own a reference to that other object. When you use this type of parameter, if you do not assign a new reference to the parameter, it will be set to null.
"q" is again of the same type. This case is treated like "p" with the important differences that the function may choose not to change the reference, and may access the object refered to. Vala will ensure that in this instance "q" actually refers to any object, and is not set to null.
Nullable Types
By default, Vala will make sure that all reference point to actual objects. This means that you can't arbitrarily assign null to a variable. If it is allowable for a reference to be null you should declare the type with a ? modifier. This allows you to assert which types may be null, and so hopefully avoid related errors in your code.
This modifier can be placed both on parameter types and return types of functions:
string? function_name(string? param) { ... }
These checks are performed at run time. For release versions of a program, when the code is fully debugged, the checks can be disabled. See the valac documentation for how to do this.
(NB: In Vala versions up to 0.1.6, you declared which variables could not be null, using the ! modifier, instead of declaring which can be null as you must do now.)
Weak References
Normally when creating an object in Vala you are returned a reference to it. Specifically this means that as well as being passed a pointer to the object in memory, it is also recorded in the object itself that this pointer exists. Similarly, whenever another reference to the object is created, this is also recorded. As an object knows how many references there are to it, it can automatically be removed when needed. This is the basis of Vala's memory management.
Weak References conversely are not recorded in the object they reference. This allows the object to be removed when it logically should be, regardless of the fact that there might be still references to it. The usual way to achieve this is with a function defined to return a weak reference, e.g.:
public static class Test { static Object o; public weak Object get_weak_ref() { o = new Object(); return o; } }
When calling this function, in order to collect a reference to the returned object, you must expect to receive a weak reference:
weak Object o = get_weak_ref();
The reason for this seemingly overcomplicated example because of the concept of ownership. If the Object "o" was not stored in the class, then when the function "get_weak_ref" returned, "o" would become unowned (i.e. there would be no references to it). If this were the case, the object would be deleted and the function would never return a valid reference. If the return value was not defined as weak, the ownership would pass to the calling code, but a weak reference cannot transfer ownership.
Weak references play a similar role to pointers, described next. They are however much simpler to use, and can be easily combined with normal references, making them useful more easily and more often. They aren't though that widely used, and probably don't need to be.
caller \ callee |
|
weak |
|
callee ref, caller unref |
caller ref&unref |
weak |
forbidden |
no refs |
Ownership transfer
'#' is used to transfer ownership.
As a suffix of a parameter (or property) type. it means that ownership of the object is transferred. As an operator, it can be used to avoid copies of non-reference counting classes. e.g.
Foo foo = #bar;
This means that bar will be set to null and foo inherits the reference/ownership of the object bar references.
Pointers
Pointers are Vala's way of allowing manual memory management. Normally when you create an instance of a type you receive a reference to it, and Vala will take care of destroying the instance when there are no more references left to it. By requesting instead a pointer to an instance, you take responsibility for destroying the instance when it is no longer wanted, and therefore get greater control over how much memory is used.
This functionality is not necessarily needed most of the time, as modern computers are usually fast enough to handle reference counting and have enough memory that small inefficiencies are not important. The times when you might resort to manual memory management are:
- When you specifically want to optimise part of a program.
- When you are dealing with an external library that does not implement reference counting for memory management (probably meaning one not based on gobject.)
In order to create an instance of a type, and receive a pointer to it:
Object* o = new Object();
In order to access members of that instance:
o->function_1(); o->data_1;
In order to free the memory pointed to:
delete o;
Miscellaneous
Static Classes:
A static class is one that cannot be instantiated, and therefore has no access to features such as inheritance. As such it is basically just an extension of the namespace principle, as a way to group together related functions. This is unlikely to be a vital feature, but if you are designing a library or creating a binding to one, it is a convenient way to identify functionality. To define a static class, simply add the static keyword to the definition. For an example, see the "Base64" class in the GLib VAPI.
Non-Object classes:
Classes defined as not being descended from GLib.Object are treated as a special case. Without inheriting from Object a class will not automatically include reference counting for memory management, and will no be able to include signals or properties. However, it will still be treated as a reference type, and is therefore fundamentally different from a struct.
Most of the use of this type of class is contained in existing libraries. (For example, GLib is at a lower level than GObject and so only uses this sort of class.) There are reasons to use non-object classes in new Vala code, but these are probably outside the scope of this tutorial.
Libraries
At the system level, a Vala library is exactly a C library, and so the same tools are used. In order to make the process simpler, and so that the Vala compiler can understand the process there is then an extra level of Vala specific information.
A "Vala library" is therefore, the system part:
- A system library (e.g. libgee.so)
- A pkgconfig entry (e.g. gee-1.0.pc)
Both of which are installed in the standard locations. And the vala specific files:
- A VAPI file (e.g. gee-1.0.vapi)
- An optional dependency file (e.g. gee-1.0.deps)
These files are explained later in this section. It should be noted that the library names are the same in the vala specific files as in the pkgconfig files.
Using
Using a library in Vala is largely automated if you use the valac compiler. The Vala specific libarary files make up what is known as a package. You tell the compiler that a package is needed by your program as follows:
$ valac --pkg gee-1.0 -o test test.vala
These command will mean your program can use any of the definitions in the gee-1.0.vapi file, and also any in any of the packages that gee-1.0 depends on. These dependencies would be be listed in gee-1.0.deps if there were any. In this example valac is set to build all the way to binary, and will therefore incorporate information from pkg-config to link the correct libraries. This is why the pkg-config names are also used for Vala package names.
Packages are generally used with namespaces, but they are not technically related. This means that even though your application is built with reference to the package, you must still include the required using statements in each file as appropriate, or else use the fully defined names of all symbols.
It is also possible to treat a local library (one that is not installed) as a package. For comparison, Vala itself uses an internal version of Gee. When valac is built it creates a VAPI file of this internal library and uses it roughly as follows:
$ valac --vapidir ../gee --pkg gee ...
For details of how to generate this library, see the next section or the example.
Creating
Creating.
Example Creating and Using
The following is an example of how to write a simple library in Vala, and also to compile and test it locally without having to install it first..
Save the following code to a file "test.vala". This is the actual library code, containing the functions we want to call from our main program.
using GLib; public class MyLib : Object { public void hello() { stdout.printf ("Hello World, MyLib\n"); } public int sum(int x, int y) { int result = x + y; return result; } }
Use the next command to generate "test.c", "test.h" and "test.vapi" files. These are the C versions of the library to be compiled, and the VAPI file representing the library's public interface.
$ valac -C --library test test.vala --basedir ./
Now edit the file "test.c" to say that the "test.h" file is to be found locally, rather than installed in a system location. The file should be changed to have the line:
#include "test.h"
Now compile the library:
$ gcc --shared -fPIC -o libtest.so $(pkg-config --cflags --libs gobject-2.0) test.c
Save the following code to a file called "hello.vala". This is the code that will use the library we have created.
using GLib; public class Hello : GLib.Object { public static void main(string[] args) { MyLib test = new MyLib(); test.hello(); int x = 4, y = 5; stdout.printf("Sum: %d, %d\n", x, y); int result = test.sum(x, y); stdout.printf("Result: %d\n", result); } }
Now compile the application code, telling the compiler that we want to link against the library we just created.
$ valac -X -I. -X -L. -X -ltest -o hello hello.vala test.vapi --basedir ./
We can now run the program. This command states that any required libraries will be found in the current directory.
$ LD_LIBRARY_PATH=$PWD ./hello
The output of the program should be:
Hello World, MyLib Sum: 4, 5 Result: 9
VAPI
VAPI files contain a description of the public interface of a library. When the library is written in Vala, this file is basically just an amalgamation of all public definitions from all Vala source files. When a library is written in C, the VAPI file might be more complicated, particular if the names and conventions used in the library are not totally regular. The VAPI file will in this case contain many annotations describing how the standardised Vala interface maps onto the C version.
Very often, VAPI files will exist to provide a binding in Vala of a library written in C. This process of creating this generally amounts to:
- Running vala-gen-introspect to extract metadata from the C library.
- Adding extra metadata to standardise the interface or make various other changes.
- Generating a VAPI file from the above sources using vapigen.
This is outside the scope of this tutorial, and you should see the specific instructions on the subject in the wiki.
Tools
The Vala distribution includes several programs to help you build and work with Vala applications. For more details of each tool, see the man pages.
valac
valac in the Vala compiler. It's primary function is to transform Vala code into compilable C code, though it can also automate the entire build and link project in simple cases.
The simple case for use is:
valac -o appname --pkg gee-1.0 file_name_1.vala file_name_2.vala
The o switch request that an object file is created, rather than just outputting C source files. The pkg option says that this build needs information from the "gee-1.0" package. You do no need to specify details about what libraries to link in, the package has this information internally. Finally, a list of source files is given. If you need a more complicated build process, omit the o switch, and continue the process manually, or through a script.
vala-gen-introspect
vala-gen-introspect is a tool for extracting metainformation about gobject based libraries. This can be used for creating VAPI files, and so binding the library for use in Vala programs. It is executed with the pkg-config name of a library
vapigen
vapigen creates a VAPI files from a library's metadata and any extra information required.
vala-gen-project
vala-gen-project is a simple gui tool for creating a Vala project. The project created will contain scripts to build using autotools, and is a good starting point for reasonably complicated systems.
Techniques
Using GLib
GLib includes a large set of utilities, including wrappers for most of the standard libc functions and more. These tools are available on all Vala platforms, even those which are not Posix compliant. For a complete description of all that GLib provides, see the GLib Reference Manual. That reference is related to the C API for GLib, but it is mainly very simple to work out how to translate into Vala.
The GLib functions are available in Vala through the following naming convention:
C API |
Vala |
Example |
g_topic_foobar() |
GLib.Topic.foobar() |
GLib.Path.get_basename() |
The GLib types can be used similarly:
Instantiate with |
Call an object member with |
GLib.Foo aFoo = new GLib.Foo(); |
aFoo.bar(); |
The APIs are not identical between C and Vala, but these naming rules should mean you can find the functions you need in the GLib VAPI files shipped with Vala, and from there find the parameters. This will hopefully suffice until more Vala documentation can be generated.
File Handling
For flexible file I/O and file handling see Vala/GIOSamples.
You can also use FileUtils.get_contents to load a file into a string.
string filename = "file.vala"; string content; ulong len; FileUtils.get_contents (filename, out content, out len);
Introductions to:
- Gtk
GtkBuilder
- Cairo
GStreamer?
- Others?