Vala Tutorial
Vala Tutorial
Vala Tutorial
Contents
1. Vala Tutorial
1. Introduction
1. What is Vala?
2. Who is this tutorial for?
3. Conventions
2. A First Program
1. Compile and Run
3. Basics
1. Source Files and Compilation
2. Syntax Overview
3. Comments
4. Data Types
1. Value Types
2. Strings
3. Arrays
4. Reference Types
5. Static Type Casting
6. Type Inference
5. Operators
6. Control Structures
7. Language Elements
1. Methods
2. Delegates
3. Anonymous Methods / Closures
4. Namespaces
5. Structs
6. Classes
7. Interfaces
8. Code Attributes
4. Object Oriented Programming
1. Basics
2. Construction
3. Destruction
4. Signals
5. Properties
6. Inheritance
7. Abstract Classes
8. Interfaces / Mixins
9. Polymorphism
10.Run-Time Type Information
11.Dynamic Type Casting
12.Generics
13.GObject-Style Construction
5. Advanced Features
1. Assertions and Contract Programming
2. Error Handling
3. Parameter Directions
4. Debugging
5. Collections
1. ArrayList<G>
2. HashMap<K,V>
3. HashSet<G>
4. Read-Only Views
6. Methods With Syntax Support
7. Multi-Threading
1. Threads in Vala
2. Resource Control
8. The Main Loop
9. Asynchronous Methods
1. Syntax and Example
2. Writing Your Own Async Methods
10.Weak References
11.Ownership
1. Unowned References
2. Ownership Transfer
12.Variable-Length Argument Lists
13.Pointers
14.Non-Object classes
15.D-Bus Integration
16.Profiles
6. Experimental Features
1. Chained Relational Expressions
2. Regular Expression Literals
3. Strict Non-Null Mode
7. Libraries
1. Using Libraries
2. Creating a Library
3. Example
4. Binding Libraries with VAPI Files
8. Tools
1. valac
2. vala-gen-introspect
3. vapigen
9. Techniques
1. Using GLib
1. File Handling
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, particularly 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 C# 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.
A First Program
Sadly predictable, but still:
stdout.printf("Hello, World\n");
return 0;
}
}
Of course, that is a Vala 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.
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 gobjectlibrary, 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 ofGLib.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.
stdout.printf("Hello, World\n");
stdout is an object in the GLib namespace that Vala ensures 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 call a method on an object, or to access an object's data. \n is
the escape sequence for a new line.
return 0;
valac is the Vala compiler, which will compile your Vala code into a binary. The
resulting binary will have the same name as the source file and can then be
directly executed on the machine. You can probably guess the output.
Basics
Source Files and Compilation
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
necessarily 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.
All source files for the same package are supplied as command line parameters to
the Vala compiler valac, along with compiler flags. This works similarly to how
Java source code is compiled. For example:
$ valac compiler.vala --pkg libvala
will produce a binary with the name compiler that links with the package libvala.
In fact, this is how the valac compiler is produced!
If you want the binary to have a different name or if you have passed multiple
source files to the compiler you can specify the binary name explicitly with the -
o switch:
$ valac source1.vala source2.vala -o myprogram
$ ./myprogram
If you give valac the -C switch, it won't compile your program into a binary file.
Instead it will output the intermediate C code for each of your Vala source files
into a corresponding C source file, in this case source1.c and source2.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 registered 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.
If you want to have a C header file for your project you can use the -H switch:
$ valac hello.vala -C -H hello.h
Syntax Overview
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, methods,
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.
For identifier names the same rules apply as for C identifiers: the first character
must be one of [a-z], [A-Z] or an underscore, subsequent characters may
additionally be digits [0-9]. Other Unicode characters are not allowed. It is
possible to use a reserved keyword as identifier name by prefixing it with
the@ character. This character is not part of the name. For example, you can
name a method foreach by writing @foreach, even though this is a reserved Vala
keyword.
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.
Comments
Vala allows comments in code in different ways.
// Comment continues until end of line
/**
* Documentation comment
*/
These are handled in the same way as in most other languages and so need little
explanation. Documentation comments are actually not special to Vala, but a
documentation generation tool like Valadoc will recognise them.
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.
A constant is defined by putting const before the type. However, local constants
are not yet supported, which means you can't define a constant inside a method,
only outside. The naming convention for constants is ALL_UPPER_CASE.
Value Types
Vala supports a set of the simple types as most other languages do.
• Byte, char, uchar; their names are char for historical reasons.
• Character, unichar; a Unicode character
• Integer, int, uint
• Long Integer, long, ulong
• Short Integer, short, ushort
• Guaranteed-size Integer, int8, int16, int32, int64 as well as their unsigned
siblings uint8, uint16, uint32, uint64. The numbers indicate the lengths in
bits.
• Float number, float, double
• Boolean, bool; possible values are true and false
• Compound, struct
• Enumeration, enum; represented by integer values, not as classes like Java's
enums
Here are some examples.
/* atomic types */
unichar c = 'u';
float percentile = 0.75f;
const double MU_BOHR = 927.400915E-26;
bool the_box_has_crashed = false;
/* defining a struct */
struct Vector {
public double x;
public double y;
public double z;
}
/* defining an enum */
enum WindowType {
TOPLEVEL,
POPUP
}
Most of these types may have different sizes on different platforms, except for the
guaranteed-size integer types. The sizeof operator returns the size that a variable
of a given type occupies in bytes:
ulong nbytes = sizeof(int32); // nbytes will be 4 (= 32 bits)
You can determine the minimum and maximum values of a numerical type
with .MIN and .MAX, e.g. int.MIN and int.MAX.
Strings
The data type for strings is string. Vala strings are UTF-8 encoded and immutable.
string text = "A string literal";
Vala offers a feature called verbatim strings. These are strings in which escape
sequences (such as \n) won't be interpreted, line breaks will be preserved and
quotation marks don't have to be masked. They are enclosed with triple double
quotation marks. Possible indentations after a line break are part of the string as
well.
string verbatim = """This is a so-called "verbatim string".
Verbatim strings don't process escape sequences, such as \n, \t, \\, etc.
They may contain quotes and may span multiple lines.""";
Strings prefixed with '@' are string templates. They can evaluate embedded
variables and expressions prefixed with '$':
int a = 6, b = 7;
string s = @"$a * $b = $(a * b)"; // => "6 * 7 = 42"
The equality operators == and != compare the content of two strings, contrary to
Java's behaviour which in this case would check for referential equality.
You can slice a string with [start:end]. Negative values represent positions relative
to the end of the string:
string greeting = "hello, world";
string s1 = greeting[7:12]; // => "world"
string s2 = greeting[-4:-2]; // => "or"
Note that indices in Vala start with 0 as in most other programming languages.
You can access a single Unicode character of a string with [index]:
unichar c = greeting[7]; // => 'w'
However, you cannot assign a new character to this position, since Vala strings
are immutable. Also note that this is not a simple array access. The string has to
be parsed internally, because with UTF-8 encoding each character can occupy a
different amount of bytes.
Many of the basic types have reasonable methods for parsing from and converting
to strings, for example:
bool b = "false".to_bool(); // => false
int i = "-52".to_int(); // => -52
double d = "6.67428E-11".to_double(); // => 6.67428E-11
string s1 = true.to_string(); // => "true"
string s2 = 21.to_string(); // => "21"
Two useful methods for writing and reading strings to/from the console (and for
your first explorations with Vala) are stdout.printf() and stdin.read_line():
stdout.printf("Hello, world\n");
stdout.printf("%d %g %s\n", 42, 3.1415, "Vala");
string input = stdin.read_line();
int number = stdin.read_line().to_int();
You already know stdout.printf() from the Hello World example. Actually, it can
take an arbitrary number of arguments of different types, whereas the first
argument is a format string, following the same rules as C format strings. If you
must output an error message you can use stderr.printf() instead ofstdout.printf().
Arrays
An array is declared by giving a type name followed by [] and created by 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 writeObject[] a = new Object[10] no
objects will be created, just the array to store them in.
int[] a = new int[10];
int[] b = { 2, 4, 6, 8 };
You can slice an array with [start:end]:
int[] c = b[1:3]; // => { 4, 6 }
Slicing an array results in a new array. Modifications to this new array have no
effect on the original array.
Multi-dimensional arrays are defined with [,] or [,,] etc.
int[,] c = new int[3,4];
int[,] d = {{2, 4, 6, 8},
{3, 5, 7, 9},
{1, 3, 5, 7}};
d[2,3] = 42;
This sort of array is represented by a single contiguous memory block. Jagged
multi-dimensional arrays ([][], also known as "stacked arrays" or "arrays of
arrays"), where each row may have a different length, are not yet supported.
You can append array elements dynamically with the += operator. However, this
works only for locally defined or private arrays. The array is automatically
reallocated if needed. Internally this reallocation happens with sizes growing in
powers of 2 for run-time efficiency reasons. However,.length holds the actual
number of elements, not the internal size.
int[] e = {}
e += 12;
e += 5;
e += 37;
If you put the square brackets after the identifier together with an indication of
size you will get a fixed-size array. Fixed-size arrays are allocated on the stack (if
used as local variables) or in-line allocated (if used as fields) and you can't
reallocate them later.
int f[10]; // no 'new ...'
Vala does not do any bounds checking for array access at runtime. If you need
more safety you should use a more sophisticated data structure like anArrayList.
You will learn more about that later in the section about collections.
Reference Types
The reference types are all types declared as a class, regardless of whether they
are descended from GLib's Object or not. 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 the memory for you. The value of a reference
that does not point anywhere is null. More on classes and their features in the
section about object oriented programming.
/* defining a class */
class Track : GLib.Object { /* subclassing 'GLib.Object' */
public double mass; /* a public field */
public double name { get; set; } /* a public property */
private bool terminated = false; /* a private field */
public void terminate() { /* a public method */
terminated = true;
}
}
Static Type Casting
In Vala, you can cast a variable from one type to another. For a static type cast, a
variable is casted by the desired type name with parenthesis. A static cast doesn't
impose any runtime type safety checking. It works for all Vala types. For example,
int i = 10;
float j = (float) i;
Vala supports another casting mechanism called dynamic cast which performs
runtime type checking and is described in the section about object oriented
programming.
Type Inference
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. The type is inferred from the right hand side of the assignment. It helps
reducing unnecessary redundance in your code without sacrificing static typing:
var p = new Person(); // same as: Person p = new Person();
var s = "hello"; // same as: string s = "hello";
var l = new List<int>(); // same as: List<int> l = new List<int>();
var i = 10; // same as: int i = 10;
This only works for local variables. Type inference is especially useful for types
with generic type arguments (more on these later). Compare
MyFoo<string, MyBar<string, int>> foo = new MyFoo<string, MyBar<string, int>>();
vs.
var foo = new MyFoo<string, MyBar<string, int>>();
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. The + operator can also
concatenate strings.
+=, -=, /=, *=, %=
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 justa = ~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 the case of 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. For strings these operators compare the
lexicographical order.
!, &&, ||
logic operations: not, and, or. These operations can be applied to Boolean values -
the first taking just one value the others two.
? :
ternary conditional operator. Evaluates a condition and returns either the value of
the left or the right sub-expression based on whether the condition is true or
false: condition ? value if true : value if false
??
checks if the right operand contains the left operand. This operator works on
arrays, strings, collections or any other type that has an
appropriatecontains() method. For strings it performs a substring search.
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 the 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 0, 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\n"); }
else if (a < 0) { stdout.printf("a is less than 0\n"); }
else { stdout.printf("a is equal to 0\n"); }
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\n");
break;
case 2:
case 3:
stdout.printf("two or three\n");
break;
default:
stdout.printf("unknown\n");
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, except for empty
cases. In order to ensure this, each non-empty case must end with
a break, return or throw statement. It is possible to use switch statements with
strings.
A note for C programmers: conditions must always evaluate to a Boolean value.
This means that if you want to check a variable for null or 0 you must do this
explicitly: if (object != null) { } or if (number != 0) { }.
Language Elements
Methods
Functions are called methods in Vala, regardless of whether they are defined
inside a class or not. From now on we will stick to the term method.
int method_name(int arg1, Object arg2) {
return 1;
}
This code defines a method, having the name method_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 method will return an integer, which in
this case is 1.
All Vala methods are C functions, and therefore take an arbitrary number of
arguments and return one value (or none if the method is declared void). 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 Directions" section in
the advanced part of this tutorial.
The naming convention for methods in Vala is all_lower_case with underscores as
word separators. This may be a little bit unfamiliar to C# or Java programmers
who are accustomed to CamelCase or mixedCamelCase method names. But with
this style you will be consistent with other Vala and C/GObject libraries.
It is not possible to have multiple methods with the same name but different
signature within the same scope ("method overloading"):
void draw(string text) { }
void draw(Shape shape) { } // not possible
This is due to the fact that libraries produced with Vala are intended to be usable
for C programmers as well. In Vala you would do something like this instead:
void draw_text(string text) { }
void draw_shape(Shape shape) { }
By choosing slightly different names you can avoid a name clash. In languages
that support method overloading it is often used for providing convenience
methods with less parameters that chain up to the most general method:
void f(int x, string s, double z) { }
void f(int x, string s) { f(x, s, 0.5); } // not possible
void f(int x) { f(x, "hello"); } // not possible
In this case you can use Vala's default argument feature for method parameters in
order to achieve a similar behaviour with just one method. You can define default
values for the last parameters of a method, so that you don't have to pass them
explicitly to a method call:
void f(int x, string s = "hello", double z = 0.5) { }
Some possible calls of this method might be:
f(2);
f(2, "hi");
f(2, "hi", 0.75);
It's even possible to define methods with real variable-length argument lists
(varargs) like stdout.printf(), although not necessarily recommended. You will
learn how to do that later.
Vala performs a basic nullability check on the method parameters and return
values. If it is allowable for a method parameter or a return value to benull, the
type symbol should be postfixed with a ? modifier. This extra information helps the
Vala compiler to perform static checks and to add runtime assertions on the
preconditions of the methods, which may help in avoiding related errors such as
dereferencing a null reference.
string? method_name(string? text, Foo? foo, Bar bar) {
// ...
}
In this example text, foo and the return value may be null, however, bar must not
be null.
Delegates
delegate void DelegateType(int a);
Delegates represent methods, allowing chunks of code to be passed around like
objects. The example above defines a new type named DelegateTypewhich
represents methods taking an int and not returning a value. Any method that
matches this signature may be assigned to a variable of this type or passed as a
method argument of this type.
delegate void DelegateType(int a);
void f1(int a) {
stdout.printf("%d\n", a);
}
void main() {
f2(f1, 5); // Passing a method as delegate argument to another method
}
This code will execute the method f2, passing in a reference to method f1 and the
number 5. f2 will then execute the method f1, passing it the number.
Delegates may also be created locally. A member method can also be assigned to
a delegate, e.g,
class Foo {
void main() {
PrintIntFunc p1 = (a) => { stdout.printf("%d\n", a); };
p1(10);
// Curly braces are optional if the body contains only one statement:
PrintIntFunc p2 = (a) => stdout.printf("%d\n", a);
p2(20):
}
Passing an anonymous method to another method:
delegate int Comparator(int a, int b);
void main() {
int[] data = { 3, 9, 2, 7, 5 };
// An anonymous method is passed as the second argument:
my_sorting_algorithm(data, (a, b) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
});
}
Anonymous methods are real closures. This means you can access the local
variables of the outer method within the lambda expression:
delegate int IntOperation(int i);
IntOperation curried_add(int a) {
return (b) => a + b; // 'a' is an outer variable
}
void main() {
stdout.printf("2 + 4 = %d\n", curried_add(2)(4));
}
In this example curried_add (see Currying) returns a newly created method that
preserves the value of a. This returned method is directly called afterwards with 4
as argument resulting in the sum of the two numbers.
Namespaces
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 either use qualified names for anything within the name of
the namespace, or be in a file with an appropriate using declaration in order to
import this namespace:
using NameSpaceName;
// ...
For example, if the namespace Gtk is imported with using Gtk; you can simply
write Window instead of Gtk.Window. A fully qualified name would only be
necessary in case of ambiguity, for example between GLib.Object and Gtk.Object.
The namespace GLib is imported by default. Imagine an invisible using GLib; line
at the beginning of every Vala file.
Everything that you don't put into a separate namespace will land in the
anonymous global namespace. If you have to reference the global namespace
explicitly due to ambiguity you can do that with the global:: prefix.
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
that 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.
Structs
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 also may have private members, meaning the
explicit public access modifier is required.
struct Color {
public double red;
public double green;
public double blue;
}
This is how you can initialise a struct:
// without type inference
Color c1 = Color();
Color c2 = { 0.5, 0.5, 1.0 };
Color c3 = Color() {
red = 0.5,
green = 0.5,
blue = 1.0
};
Classes
class ClassName : SuperClassName, InterfaceName {
}
defines a class, i.e. a reference type. In contrast to structs, instances of classes
are heap allocated. There is much more syntax related to classes, which is
discussed more fully in the section about object oriented programming.
Interfaces
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 its abstract methods in a non-abstract
class. Vala interfaces are more powerful than Java or C# interfaces. In fact, they
can be used as mixins. The details of interfaces are described in the section about
object oriented programming.
Code Attributes
Code attributes instruct the Vala compiler details about how the code is supposed
to work on the target platform. Their syntax
is [AttributeName] or[AttributeName(param1 = value1, param2 = value2, ...)].
They are mostly used for bindings in vapi files, [CCode(...)] being the most
prominent attribute here. Another example is the [DBus(...)] attribute for
exporting remote interfaces via D-Bus.
Basics
A simple class may be defined as follows:
public class TestClass : GLib.Object {
/* Fields */
public int first_data = 0;
private int second_data;
/* Constructor */
public TestClass() {
this.second_data = 5;
}
/* Method */
public int method_1() {
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 type system) that contains three members. There are two
data members, the integers defined at the top, and one method called method_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 recognise 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. Vala supports four different access modifiers:
public No restrictions to access
Access is limited to within the class/struct definition. This is the default if no access
private
modifier is specified
protected Access is limited to within the class definition and any class that inherits from the class
internal Access is limited exclusively to classes defined within the same package
The constructor initialises new instances of a class. It has the same name as the
class, may take zero or more arguments and is defined without return type.
The final part of this class is a method definition. This method is to be
called method_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.method_1();
Construction
Vala supports two slightly different construction schemes: the Java/C#-style
construction scheme which we will focus on for now, and the GObject-style
construction scheme which will be described in a section at the end of the
chapter.
Vala does not support constructor overloading for the same reasons that method
overloading is not allowed, which means a class may not have multiple
constructors with the same name. However, this is no problem because Vala
supports named constructors. If you want to offer multiple constructors you may
give them different name additions:
public class Button : Object {
public Button() {
}
void main() {
var p1 = new Point.rectangular(5.7, 1.2);
var p2 = new Point.polar(5.7, 1.2);
}
Destruction
Although Vala manages the memory for you, you might need to add your own
destructor if you choose to do manual memory management with pointers (more
on that later) or if you have to release other resources. The syntax is the same as
in C# and C++:
class Demo : Object {
~Demo() {
stdout.printf("in destructor");
}
}
Since Vala's memory management is based on reference counting instead
of tracing garbage collection, destructors are deterministic and can be used to
implement the RAII pattern for resource management (closing streams, database
connections, ...).
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 identical methods (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
theconnect() method. In order to dive right in at the deep end, the following
example also introduces lambda expressions, a very useful way to write signal
handling code in Vala:
public class Test : GLib.Object {
public signal void sig_1(int a);
t1.sig_1.connect((t, a) => {
stdout.printf("%d\n", a);
});
t1.sig_1(5);
return 0;
}
}
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 method 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
expression. The definition states that the method 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 parameters 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.
Note: Since April 2010 signals can be annotated with any combination of flags:
[Signal (action=true, detailed=true, run=true, no_recurse=true, no_hooks=true)]
public signal void sig_1 ();
Properties
It is good object oriented programming practice to hide implementation
details from the users of your classes (information hiding principle), so you can
later change the internals without breaking the public API. One practice is to make
fields private and provide accessor methods for getting and setting their values
(getters and setters).
If you're a Java programmer you will probably think of something like this:
class Person : Object {
private int age = 32;
/* Property */
public int age {
get { return _age; }
set { _age = value; }
}
}
This syntax should be familiar to C# programmers. A property has a get and
a set block for getting and setting its value. value is a keyword that represents the
new value that should be assigned to the property.
Now you can access the property as if it was a public field. But behind the scenes
the code in the get and set blocks is executed.
var alice = new Person();
alice.age = alice.age + 1; // or even shorter:
alice.age++;
If you only do the standard implementation as shown above then you can write
the property even shorter:
class Person : Object {
/* Property with standard getter and setter and default value */
public int age { get; set; default = 32; }
}
With properties you can change the internal working of classes without changing
the public API. For example:
static int current_year = 2525;
public SubClass() {
base(10);
}
}
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_method 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_method 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.
With base a constructor of a subclass can chain up to a constructor of its base
class.
Abstract Classes
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.
A class containing abstract methods must be declared abstract as well. The result
of this is to prevent any instantiation of the type.
public abstract class Animal : Object {
Interfaces / Mixins
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 method_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 "method_1" is a
method. Here it is declared that this method must be implemented by classes that
implement this interface.
The simplest possible full implementation of this interface is:
public class Test1 : GLib.Object, ITest {
public int data_1 { get; set; }
public void method_1() {
}
}
And may be used as follows:
var t = new Test1();
t.method_1();
ITest i = t;
i.method_1();
Interfaces in Vala may not inherit from other interfaces, but they may declare
other interfaces to be prerequisites, 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 prerequisite classes.
There's another important difference between Vala interfaces and Java/C#
interfaces: Vala interfaces may have non-abstract methods! Vala actually allows
method implementations in interfaces, hence the requirement that abstract
methods be declared abstract. Due to this fact Vala interfaces can act as mixins.
This is a restricted form of multiple inheritance.
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 void method_1() {
stdout.printf("SuperClass.method_1()\n");
}
}
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 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 methods 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 = wrapper.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"
method will happily accept the object it is passed, and treat its wrapped object as
though it was a GLib.Object instance.
GObject-Style Construction
As pointed out before, Vala supports an alternative construction scheme that is
slightly different to the one described before, but closer to the way GObject
construction works. Which one you prefer depends on whether you come from the
GObject side or from the Java or C# side. The gobject-style construction scheme
introduces some new syntax elements: construct properties, a
special Object(...) call and a construct block. Let's take a look at how this works:
public class Person : Object {
/* Construction properties */
public string name { get; construct; }
public int age { get; construct set; }
construct {
// do anything else
stdout.printf("Welcome %s\n", this.name);
}
}
With the gobject-style construction scheme each construction method only
contains an Object(...) call for setting so-called construct properties.
TheObject(...) call takes a variable number of named arguments in the form
of property: value. These properties must be declared as constructproperties.
They will be set to the given values and afterwards all construct {} blocks in the
hierarchy from GLib.Object down to our class will be called.
The construct block is guaranteed to be called when an instance of this class is
created, even if it is created as a subtype. It does neither have any parameters,
nor a return value. Within this block you can call other methods and set member
variables as needed.
Construct 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
construct property, it is possible to write a custom construct block for the
property, which will be executed immediately on assignment, and before any
other construction code.
If a construct property is declared without set it is a so-called construct
only property, which means it can only be assigned on construction, but no longer
afterwards. In the example above name is such a construct only property.
Here's a summary of the various types of properties together with the
nomenclature usually found in the documentation of gobject-based libraries:
public int a { get; private set; } // Read
public int b { private get; set; } // Write
public int c { get; set; } // Read / Write
public int d { get; set construct; } // Read / Write / Construct
public int e { get; construct; } // Read / Write / Construct Only
Advanced Features
Assertions and Contract Programming
With assertions a programmer can check assumptions at runtime. The syntax
is assert(condition). If an assertion fails the program will terminate with an
appropriate error message. There are a few more assertion methods within the
GLib standard namespace, e.g.:
assert_not_reached()
return_if_fail(bool expr)
return_if_reached()
warn_if_fail(bool expr)
warn_if_reached()
You might be tempted to use assertions in order to check method arguments
for null. However, this is not necessary, since Vala does that implicitly for all
parameters that are not marked with ? as being nullable.
void method_name(Foo foo, Bar bar) {
/* Not necessary, Vala does that for you:
return_if_fail(foo != null);
return_if_fail(bar != null);
*/
}
Vala supports basic contract programming features. A method may have
preconditions (requires) and postconditions (ensures) that must be fulfilled at the
beginning or the end of a method respectively:
double method_name(int x, double d)
requires (x > 0 && x < 10)
requires (d >= 0.0 && d <= 1.0)
ensures (result >= 0.0 && result <= 10.0)
{
return d * x;
}
result is a special variable representing the return value.
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 method. If a method, for example,
requires a number greater than 0 as a parameter, it should fail on negative values
using contract programming techniques such as preconditions or assertions
described in the previous section.
Vala errors are so-called checked exceptions, which means that errors must get
handled at some point. However, if you don't catch an error the Vala compiler will
only issue a warning without stopping the compilation process.
Using exceptions (or errors in Vala terminology) is a matter of:
1) Declaring that a method may raise an error:
void my_method() throws IOError {
// ...
}
2) Throwing the error when appropriate:
if (something_went_wrong) {
throw new IOError.FILE_NOT_FOUND("Requested file could not be found.");
}
3) Catching the error from the calling code:
try {
my_method();
} catch (IOError e) {
stdout.printf("Error: %s\n", e.message);
}
4) Comparing error code by "is" operator
Toggle line numbers
1 IOChannel channel;
2 try {
3 channel = new IOChannel.file("/tmp/my_lock", "w");
4 } catch (FileError e) {
5 if(e is FileError.EXIST) {
6 throw e;
7 }
8 GLib.error("", e.message);
9 }
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, calledfinally. 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
}
Parameter Directions
A method in Vala is passed zero or more arguments. The default behaviour when
a method is called is as follows:
• Any value type parameters are copied to a location local to the method as it
executes.
• Any reference type parameters are not copied, instead just a reference to
them is passed to the method.
This behaviour can be changed with the modifiers 'ref' and 'out'.
'out' from the caller side
you may pass an uninitialised variable to the method and you may expect it
to be initialised after the method returns
'out' from callee side
the parameter is considered uninitialised and you have to initialise it
'ref' from caller side
the variable you're passing to the method has to be initialised and it may be
changed or not by the method
'ref' from callee side
the parameter is considered initialised and you may change it or not
Debugging
For demonstration purposes we will create a buggy program by intentionally
dereferencing a null reference, which will result in a segmentation fault:
class Foo : Object {
public int field;
}
void main() {
Foo? foo = null;
stdout.printf("%d\n", foo.field);
}
$ valac debug-demo.vala
$ ./debug-demo
Segmentation fault
So how do we debug this program? The -g command line option tells the Vala
compiler to include Vala source code line information in the compiled binary, --
save-temps keeps the temporary C source files:
$ valac -g --save-temps debug-demo.vala
Vala programs can be debugged with the GNU Debugger gdb or one of its
graphical front-ends, e.g. Nemiver.
$ nemiver debug-demo
Collections
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.
If you want to use Gee in your own application, install the library separately on
your system. Gee can be obtained from http://live.gnome.org/Libgee. In order to
use the library you must compile your programs with --pkg gee-1.0.
The fundamental types of collection are:
• Lists: Ordered collections of items, accessible by numeric index.
• Sets: Unordered collections of distinct.
• Maps: Unordered collection of items, accessible 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
methods, 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 or 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.
Full Gee API documentation, Gee Examples
Some important Gee classes are:
ArrayList<G>
Implementing: Iterable<G>, Collection<G>, 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: Iterable<Entry<K,V>>, 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 functions for hashing and testing equality of keys in specific ways.
You can optionally pass custom hash and equal functions to the constructor, for
example:
var map = new Gee.HashMap<Foo, Object>(foo_hash, foo_equal);
For strings and integers the hash and equal functions are detected automatically,
objects are distinguished by their references by default. You have to provide
custom hash and equal functions only if you want to override the default
behaviour.
HashSet<G>
Implementing: Iterable<G>, Collection<G>, 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 functions for
hashing and testing equality of keys in specific ways.
Read-Only Views
You can get a read-only view of a collection via the read_only_view property,
e.g. my_map.read_only_view. This will return a wrapper that has the same
interface as its contained collection, but will not allow any form of modification, or
any access to the contained collection.
void main() {
var even = new EvenNumbers();
stdout.printf("%d\n", even[5]); // get()
if (4 in even) { // contains()
stdout.printf(@"$even\n"); // to_string()
}
foreach (int i in even) { // iterator()
stdout.printf("%d\n", i);
if (i == 20) break;
}
}
Multi-Threading
Threads in Vala
A program written in Vala may have more than 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 methods of the Thread class in GLib, as shown in the
following (very simplified) example:
void* thread_func() {
stdout.printf("Thread running.\n");
return null;
}
try {
Thread.create(thread_func, false);
} catch (ThreadError e) {
return 1;
}
return 0;
}
This short program will request a new thread be created and executed. The code
to be run being that in thread_func. Also note the test at the start of the main
method - a Vala program will not be able to use threads unless compiled
appropriately, so if 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 threading-sample.vala
This will both include required libraries and make sure the threading system is
initialised whenever possible.
The program will now run without segmentation faults, 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 was placed at the
end of the above main method, 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 join() method. Calling
this method on a Thread object 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:
try {
unowned Thread thread = Thread.create(thread_func, true);
thread.join();
} catch (ThreadError e) {
return 1;
}
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 - an unowned reference to a Thread object (unowned 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 method 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 simultaneously, there is a
chance that data are 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; }
time.set_callback(() => {
stdout.printf("Time!\n");
loop.quit();
return false;
});
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()' method. 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+
methods to control the main loop.
void main(string[] args) {
Gtk.init(ref args);
var time = new TimeoutSource(2000);
time.set_callback(() => {
stdout.printf("Time!\n");
Gtk.main_quit();
return false;
});
time.attach(null);
Gtk.main();
}
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.
Asynchronous Methods
With asynchronous methods it is possible to do programming without any
blocking. Since version 0.7.6 Vala provides a special syntax for asynchronous
programming.
void main() {
list_dir.begin();
new MainLoop().run();
}
list_dir() will not block. Within list_dir() the async
methods enumerate_children_async() and next_files_async() are called with
the yield keyword.list_dir() continues executing as soon as a result is returned to
it.
MainLoop main_loop;
void main() {
var test = new Test();
run.begin(test);
Weak References
Vala's memory management is based on automatic reference counting. Each time
an object is assigned to a variable its internal reference count is increased by 1,
each time a variable referencing an object goes out of scope its internal reference
count is decreased by 1. If the reference count reaches 0 the object will be freed.
However, it is possible to form a reference cycle with your data structures. For
example, with a tree data structure where a child node holds a reference to its
parent and vice versa, or a doubly-linked list where each element holds a
reference to its predecessor and the predecessor holds a reference to its
successor.
In these cases objects could keep themselves alive simply by referencing to each
other, even though they should be freed. To break such a reference cycle you can
use the weak modifier for one of the references:
class Node {
public weak Node prev;
public Node next;
}
This topic is exlpained in detail on this page: Vala's Memory Management
Explained.
Ownership
Unowned 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.
Unowned 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 method defined to return an unowned reference, e.g.:
class Test {
private Object o;
void main() {
method_with_varargs(42, "foo", 0.75, "bar", 0.25, "baz", 0.32);
}
It checks for null as a sentinel to recognize the end of the varargs list. Vala always
implicitly passes null as the last argument of a varargs method call.
Varargs have a serious drawback that you should be aware of: they are not type-
safe. The compiler can't tell you whether you are passing arguments of the right
type to the method or not. That's why you should consider using varargs only if
you have a good reason, for example: providing a convenience function for C
programmers using your Vala library, binding a C function. Often an array
argument is a better choice.
A common pattern with varargs is to expect alternating string - value pairs as
arguments, usually meaning gobject property - value. In this case you can
write property: value instead, e.g.:
actor.animate (AnimationMode.EASE_OUT_BOUNCE, 3000, x: 100.0, y: 200.0,
rotation_angle_z: 500.0, opacity: 0);
is equivalent to:
actor.animate (AnimationMode.EASE_OUT_BOUNCE, 3000, "x", 100.0, "y", 200.0,
"rotation-angle-z", 500.0, "opacity", 0);
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->method_1();
o->data_1;
In order to free the memory pointed to:
delete o;
Vala also supports the address-of (&) and indirection (*) operators known from C:
int i = 42;
int* i_ptr = &i; // address-of
int j = *i_ptr; // indirection
Non-Object classes
Classes defined as not being descended from GLib.Object are treated as a special
case. They are derived directly from GLib's type system and therefore much
lighter in weight. In a more recent Vala compiler, one can also implement
interfaces, signals and properties with these classes.
One obvious case of using these non-Object classes stays in the GLib bindings.
Because GLib is at a lower level than GObject, most classes defined in the binding
are of this kind. Also, as mentioned before, the lighter weight of non-object
classes make them useful in many practical situations (e.g. the Vala compiler
itself). However the detailed usage of non-Object classes are outside the scope of
this tutorial. Be aware that these classes are fundamentally different from structs.
D-Bus Integration
D-Bus is tightly integrated into the language and has never been easier than with
Vala.
To export a custom class as a D-Bus service you just need to annotate it with
the DBus code attribute and register an instance of this class with your local D-
Bus session.
[DBus(name = "org.example.DemoService")]
public class DemoService : Object {
/* Private field, not exported via D-Bus */
int counter;
You can also use a graphical D-Bus debugger like D-Feet to browse your D-Bus
interfaces and call methods.
Some comprehensive
examples: Vala/DBusClientSamples and Vala/DBusServerSample
Profiles
Vala supports a couple of different profiles:
• gobject (default)
• posix
• dova
A profile determines what language features are available and on which libraries
the resulting C-Code will depend.
To select a different profile use valac's --posix switch, e.g.:
valac --profile=posix somecode.vala
Experimental Features
Some features of Vala are experimental. This means they are not fully tested and
might be subject to changes in future versions.
if (0 < a && a < b && b < c && c < d && d < 255) {
// do something
}
in a more natural way:
if (1 < a < 5) {}
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 pkg-config 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 pkg-config files.
Using Libraries
Using a library in Vala is largely automated if you use the valac compiler. The Vala
specific library 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 test.vala
This command means 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 qualified 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 valacis built it
creates a VAPI file of this internal library and uses it roughly as follows:
$ valac --vapidir ../gee --pkg gee ...
For details on how to generate this library, see the next section or the example.
Creating a Library
Vala is not yet capable of directly creating dynamic or static libraries. To create a
library, proceed with the -c (compile only) switch and link the object files with your
favourite linker, i.e. libtool or ar.
$ valac -c ...(source files)
$ ar cx ...(object files)
Example
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.
public class MyLib : Object {
Save the following code to a file called hello.vala. This is the code that will use the
library we have created.
void main() {
var test = new MyLib();
test.hello();
int x = 4, y = 5;
stdout.printf("The sum of %d and %d is %d\n", x, y, test.sum(x, y));
}
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
You can also create a GObjectIntrospection GIR file for your library with the --
gir option:
valac -C test.vala --library test --gir Test-1.0.gir
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 is 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 requests 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 not 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, use the -C switch to generate C files
instead of a binary, 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.
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 foo = new GLib.Foo(); foo.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.
<span class="Special" sty