The Genie Programming Language Tutorial
Contents
- What is Genie?
- Hello World
- Hello World Explained
- Compiling and Genie's Relation to C
- Technical Documentation
- Identifiers
- Data Values
- Operators
- Control Flow Statements
- Scope of Identifiers
- Identifiers and Type
- Functions
- 'try...except' Blocks
- Objects
- Special Operators
- Collections
- Interfacing with a C Library
- Concurrency
- Inter-Process Communication
- User Interfaces
- Attributes
- Advanced Memory Management
- Using Genie and Vala together
- More Resources
What is Genie?
Genie is a programming language, in the same vein as Vala. Genie allows for a more modern programming style while being able to effortlessly create and use GObjects natively with Linux, *BSD, Windows or OS X.
The syntax is designed to be clean, clear and concise and is derived from numerous modern languages like Python, Boo, D language and Delphi. For a sample, take a look at instance.gs from the Khövsgöl project.
Genie is very similar to Vala in functionality but differs in syntax allowing the developer to use cleaner and less code to accomplish the same task.
Like Vala, Genie has the same advantages:
Programs written in Genie should have similar performance and resource usage to those written directly in Vala and C
- Genie produces C code that compiles to an executable binary. This means Genie has none of the bloat and overhead that comes with many other high level languages which utilize a VM (e.g. Python, Mono, Java)
Classes in Genie are actually GObjects so Genie can be used for creating platform code like widgets and libraries where GObjects are required for binding to other languages.
Hello World
As Hello World is the de facto first sample program, here is Genie's in all its glory:
init
print "Hello World"
Provided you have Vala installed, copy and paste the code above and save it in a file called "hello.gs". Genie code must be written in files with the *.gs extension.
Compile and run it by simply issuing the following on a UNIX-like command line in the same directory you saved "hello.gs":
$ valac hello.gs $ ./hello
When you want to compile Genie code, you give the compiler a list of the files required, and the Genie/Vala compiler will work out how they fit together.
If you give valac the -C switch, it will create a C source code file called "hello.c" instead of an executable binary file.
Hello World Explained
As you can see, Genie is pretty clean and concise so the above example wont need much explaining...
init Block
An init block declared outside of a class or struct is equivalent to a "main" function in C and only one of these may be present. Any code that needs to be run at start up should be present here.
Block Indentation
Block indentation in Genie uses tabs by default. So in the Hello World example above, a tab appears prior to the keyword print.
It's also possible to tell it to use a certain number of spaces in lieu of a tab by ensuring the first line of the source file contains the following attribute:
[indent=2]
init
print "Hello World"
That attribute tells Genie to ignore tabs and use 2 spaces to signify a block indentation. An indent=0 means use a tab.
Code examples on this page will always use [indent=4] as this wiki converts tabs to spaces in code examples. This means no tabs would be copied in a copy and paste of the code, unless the tabs had been entered as HTML entities in the wiki.
Case and Unicode
Genie is case sensitive - so be careful when mixing case in your code. Keywords are always lower case. Identifiers can be mixed case, but must be in the Unicode U+0020 to U+007E range. This is the ASCII printable characters range as a result of it being compiled into C code. For example:
init
pi:double = Math.PI
print pi.to_string()
would compile, but
init
π:double = Math.PI
print π.to_string()
would produce an error because the symbol π is outside the ASCII printable range of characters.
Genie can, however, handle Unicode data very well. Here, is an example of Hello World with Unicode characters outside the ASCII range:
init
Intl.setlocale( LocaleCategory.ALL, "" )
print "Hello World! 你好世界"
C libraries use the US English, ASCII encoding, by default as part of the C standard. The setlocale command changes this to the locale of the machine and if this can handle Unicode and has Chinese glyphs installed then the Chinese version of Hello World will be displayed.
Compiling and Genie's Relation to C
This section gives an overview of Genie's relation to the C programming language. There are two useful points for anyone getting to know Genie. The first is of immediate practical use and that is Genie's relation to C libraries. C libraries can be easily used in Genie when a VAPI (Vala Application Programming Interface) file exists. The second is to gain an overview of what is going on in the background when valac is run. This will help to understand compiler feedback messages, organise larger projects, develop the Genie parser itself, etc.
When the command
valac hello.gs
was issued the stages valac followed are illustrated below:
In the first stage valac used the Genie parser to convert the hello.gs file into an internal representation. Note that .vala files can be given as parameters to valac at the same time, so Genie and Vala files are compiled to the same binary.
In the second stage valac checked the internal representation was meaningful code, for example that identifiers were defined, and then produced the C code.
In the third stage valac called on a C compiler. The C compiler then produced the binary from the C code. The default C compiler is gcc, but this can be altered with the --cc=COMMAND switch. If .c and .h files were also given as parameters to valac then these files will be passed to the C compiler and compiled to the same binary.
Using a C Library
Genie and Vala maintain a high level of compatibility with C programs and libraries. This is a powerful feature because it allows you to select from many efficient and well tested C libraries to use in your Genie code. A list of bindings is maintained on the Vala wiki.
A translation file is used by the Vala compiler to translate the Genie or Vala syntax into the correct C code for use with a specific C library. A translation file is called a VAPI (Vala Application Programming Interface) file. By default valac includes two VAPI files when it compiles a Genie program. These are for GLib and GObject. GLib is a cross platform general purpose utility library that works on many Unix-like platforms, Windows and Mac OS X. GLib provides things like data types, type conversions, string utilities, file utilities, a mainloop abstraction, etc. for Genie. GObject is part of GLib and provides Genie's object system.
To use a C library three things need to be done. Firstly install the library and its development files. Secondly use the --pkg switch when calling valac. Then finally use the library with its namespace. For example Genie's list and dictionary collection types are provided by libgee. To use libgee you will need to install the library. Its development files may be distributed separately, called something like libgee-devel or libgee-0.8-dev. To compile a Genie program that uses libgee:
valac --pkg gee-0.8 program.gs
The --pkg gee-0.8 tells valac to look for a VAPI file called gee-0.8.vapi and also a pkg-config file called gee-0.8.pc. The 0.8 signifies the version of the API. pkg-config files provide information on where the library files are stored on the system so they can be given automatically to the C compiler. Each library needs to be given as a separate --pkg switch:
valac --pkg gee-0.8 --pkg gio-2.0 program.gs
The VAPI will provide a namespace for the library. This is often slightly different to the VAPI filename. To use libgee without prefixing everything with Gee use the following Genie command:
uses Gee
The namespace GLib is automatically included in Genie.
libgee is written in Vala and could be a useful example of how to write a Vala or Genie program that produces a shared library and C header interface. See libgee source code.
A Simple Build Script
A project will usually be made up of multiple source files and libraries. It is often easier to manage these with a build system. For a personal project, of up to a few thousand lines of code, a single script is usually enough. By placing the source files and libraries over multiple lines in this simple script it helps to add and remove entries as the project evolves. The script would also use the --output switch in valac to produce a single named binary.
For Unix-like platforms, such as GNU/Linux and OS X, an example script would be:
#!/bin/sh
valac \
src/init.gs \
src/CLIOptions.gs \
src/Configuration.gs \
src/Logging.gs \
src/devices/DeviceFactory.gs \
src/devices/NoDevice.gs \
src/devices/BlockDevice.gs \
src/devices/FileAsDevice.gs \
--pkg gio-2.0 \
--pkg gee-0.8 \
--output exampleprogram
For Windows this can be done with a .bat file:
valac src/init.gs ^
src/CLIOptions.gs ^
src/Configuration.gs ^
--pkg gee-0.8 ^
--output exampleprogram
Compile Time Checks
The Vala compiler will run a number of checks on the Genie code. This is called static code analysis. One of the main checks carried out is to make sure data types match. This code:
init
a:int = 1
b:double = 3.5
c:int = a + b
print c.to_string()
will produce a compile time error similar to this:
type_error.gs:4.8-4.15: error: Assignment: Cannot convert from `double' to `int'
c:int = a + b
^^^^^^^^
The message identifies the source file containing the error, type_error.gs, and the line number, 4. The characters at positions 8 to 15 on the line are identified as the error and these are also highlighted in the line below.
Example C Output
Usually valac will remove the C files produced after the binary has been compiled. The temporary C files can be kept by using the --save-temps switch. To produce just the C files use the --ccode switch. For example:
valac --ccode hello.gs
will produce a file, hello.c, similar to this:
/* hello.c generated by valac 0.29.1, the Vala compiler
* generated from hello.gs, do not modify */
#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>
#include <string.h>
void _vala_main (gchar** args, int args_length1);
void _vala_main (gchar** args, int args_length1) {
g_print ("Hello World\n");
}
int main (int argc, char ** argv) {
#if !GLIB_CHECK_VERSION (2,35,0)
g_type_init ();
#endif
_vala_main (argv, argc);
return 0;
}
There is no need to learn C programming when using Genie, but understanding the process valac follows will help understand some of the messages you may encounter when compiling a Genie program. If you want to examine more deeply how Genie works you can read from the example above that the Genie print command is translated into GLib's g_print () function call.
Runtime Analysis
For testing GLib has a test framework.
There are a number of sophisticated tools for analysing binaries produced from C code. valgrind is a good tool for checking memory use and the GNU debugger, gdb, allows controlled execution of a binary. To embed Genie line numbers in the test binary use the --debug switch with valac. In most cases, however, these tools will not be needed. Genie uses reference counting for memory management. In some data structures it is possible to create reference cycles and the memory is not freed. See Vala's Memory Management Explained for more details. Memory leaks may also occur when a binding to a C library has not defined ownership correctly or there is a bug in the C library.
Technical Documentation
Documentation of code can be a burden and get out of step with the current code. These are arguments in favour of self-documenting code.
Naming Conventions
Identifiers have to be use the ASCII character set to produce compatible C code. That is the main rule about identifiers. Naming conventions are followed in the GLib programming world that are useful to know and follow:
Style | Used with | Example |
lower_snake_case | Functions / Methods |
|
UpperCamelCase | Classes |
|
Enums |
| |
Namespaces |
| |
UPPER_SNAKE_CASE | Enum values |
|
Comments
Genie allows comments in code in two different ways.
// Comment continues until end of line /* Comment lasts between delimiters */
Identifiers
An identifier is defined by its name and its type, e.g. i:int meaning an integer called i. In the case of value types this also creates an object of the given type. For reference types these just defines 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. var o = new Object() creates a new Object and makes o a reference to it.
Prefixing symbols with @ let you using reserved keywords. This is mainly useful for bindings. You can use an argument @for for example.
Data Values
Text
Text is stored in the string type and delimited between double quotes:
a:string = "Some text"
Multi-line text can be delimited between three double quotes:
a:string = """
Multi-line text
Examples of where this is useful are SQL and text menus"""
This is called a verbatim string.
Single quotes delimit a single character:
a:char = 'a'
Including a Variable's Value in a String
Genie provides a number of ways to create text templates that can include the value of a variable in a string.
String concatenation with the + operator:
init
name:string = "Genie"
print( "My name is " + name )
init
name:string = "Genie"
print( "My name is %s", name )
print( "My name is %s and everyone calls me %s", name, name )
String templates use the @ prefix before the string and string interpolation uses the $ prefix:
init
name:string = "Genie"
print( @"My name is $name" )
String templates can be over multiples lines and evaluate expressions:
init
name:string = "Genie"
print( @"My name is $name
You can call me $name
Two plus two is $(2 + 2)
The current date and time is: $( new DateTime.now_local() )" )
String templates rely on the type having a to_string() method. So in the example above, the result of 2 + 2 returns an int and int has a to_string() method. The same for the DateTime object.
Numbers
Whole Numbers
Whole numbers are represented by the integer type:
a:int = 1
This should cover most use cases when starting with Genie, but you can also set the width of the integer and make it unsigned:
init
a:int = 1
b:int8 = 2
c:int16 = 3
d:int32 = 4
e:int64 = 5
print "%i, %i, %i, %i, %lli", a, b, c, d, e
f:uint = 6
g:uint8 = 7
h:uint16 = 8
i:uint32 = 9
j:uint64 = 10
print "%u, %u, %u, %u, %llu", f, g, h, i, j
Real Numbers
Real numbers are represented by the double-precision floating point number type:
a:double = 0.5
This is C implementation specific, but will usually be IEEE 754 double-precision floating-point format.
C's "Usual Arithmetic Conversions"
Booleans
A boolean stores either true or false:
a:bool = true
Enumerations and Flags
The following code example defines an enum type. This is an enumerable sequence of values starting from zero. In this example FIRST_VALUE has the value 0 and SECOND_VALUE has the value 1:
enum MyEnum
FIRST_VALUE
SECOND_VALUE
It is possible to give a specific integer value to any entity in an enum:
enum MyEnum
FIRST_VALUE = 1
THIRD_VALUE = 3
An enum value is referred to by its type and identifier, e.g. MyEnum.FIRST_VALUE.
The [Flags] attribute marks an enum as a set of binary digits (bits). In this example Colors.RED is 1 (001 in binary), Colors.GREEN is 2 (010) and Colors.BLUE is 4 (100). Colors.YELLOW is 3 (011), a combination of RED and GREEN:
[Flags]
enum Colors
RED
GREEN
BLUE
YELLOW = RED | GREEN
MAGENTA = RED | BLUE
CYAN = GREEN | BLUE
Dates and Times
Handling dates and times is a complex area. This sub-section focuses on day to day use of dates and times on Earth. The sub-section introduces using a function provided by a library.
GLib provides classes for handling dates and times. In Valadoc see GLib.DateTime, GLib.Date and GLib.Time
Monotonic Time
Monotonic time is a timer provided by your system. GLib gives cross-platform access to a monotonic timer running at microsecond resolution. A monotonic timer is useful for timing the duration of an event. An example in Genie:
init
Intl.setlocale()
var start = get_monotonic_time()
Thread.usleep( 1000000 )
var stop = get_monotonic_time()
print( "| %20s | %20s | %20s |", "Start (µs)", "Stop (µs)", "Duration (seconds)" )
print( "|---------------------+---------------------+----------------------|" )
print( "|%20lld |%20lld | %20.6f |", start, stop, ((double)(stop - start)/1000000) )
Intl.setlocale() is needed to print the Unicode character for micro, µ, in the table header.
The example uses another GLib function, Thread.usleep (), to pause the current program for a second (one million microseconds). A reading is taken of the monotonic time before and after the sleep call. The results are then printed in a table with the elapsed time in seconds.
The monotonic time increments continuously during the running of the system. Monotonic time may not continue if the system is suspended, but that is system dependent. More information is available from the GLib documentation on get_monotonic_time ().
Operators
/* assignment */ a = 2 /* arithmetic */ a = 2+2 // 4 a = 2-2 // 0 a = 2/2 // 1 a = 2*2 // 4 a = 2%2 // 0 a++ // equivalent to a = a + 1 a-- // equivalent to a = a - 1 a += 2 // equivalent to a = a + 2 a -= 2 a /= 2 a *= 2 a %= 2 /* relational */ a > b a < b a >= b a <= b a is not b // not equal eg if a is not 2 do print "a is not 2" a is b // equality eg if a is 2 do print "a is 2" /* logical */ a and b a or b not (b) if (a > 2) and (b < 2) print "a is greater than 2 and b is less than 2" /* bitwise operators (same as C syntax) */ |, ^, &, ~, |=, &=, ^= /* bit shifting */ <<=, >>= /* Object related */ obj isa Class
Control Flow Statements
Local control flow statements can be a block or a single line. Single line control flow statements must use the do keyword between the condition and the statements to be executed. Genie also provides non-local control flow with an event based model using the async modifier and yield statement. Event based control flow is covered later in the section about concurrency.
if, while and for 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 next part of the iteration.
Conditional Execution with 'if'
if a > 0
print "a is greater than 0"
else if a is 0
print "a is equal to 0"
else
print "a is less than 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.
if a > 0 do print "a is greater than 0"
'case ... when'
case a
when 0,1,2
print "a is less than 3"
when 3
print "a is 3"
default
print "a is greater then 3"
a case...when statement runs a section of code based on the value passed to it. In Genie (unlike C) there is no fall through between cases, as soon as a match is found no more will be checked.
Genie case...when statements work with string variables, too
var s = "winter"
case s
when "winter"
print "its cold"
when "summer"
print "its warm"
default
print "its warm and cold"
Conditional Loops with 'while'
while a > b
a--
while a > b do a--
will decrement "a" repeatedly, checking before each iteration that "a" is greater than "b".
count:int = 1
do
print "%i", count
count++
while count <= 10
will always print at least one value of count
Collection Iteration with 'for'
for s in args do print s
will print out each element in the args string array or another iterable collection. The meaning of iterable will be described later.
Genie can also generate number sequences with the to and downto keywords:
for var i = 1 to 10
print "i is %d", i
will initialize "i" to 1 and then call the print statement repeatedly whilst incrementing "i" until it reaches a value of 10
Scope of Identifiers
Block Level Scope
Genie uses meaningful white space to show blocks of code. This makes it easy to identify a block of code. When an identifier is declared it can only be accessed in the block of code it is declared. Attempting to access the identifier outside of that block will result in an error from the Vala compiler.
A variable is an identifier that identifies an instance of a data type. A variable is declared with its label, a colon and its data type, e.g. a:string or b:int. It is good practise to also initialize the variable by assigning a value to the variable at the same time, e.g. a:string = "example string" or b:int = 0. This avoids an error if you try to read an uninitialized variable.
An advantage of having an identifier only accessible in the block it is declared is that the identifier can be re-used without a name clash. For example:
init
if true
a:string = "test"
print a
if true
a:string = "another"
print a
If you change the second declaration of a to be just an assignment, that is a = "another", then the Vala compiler will produce an error stating a does not exist. This is because the variable only exists in the conditional block of the if true statement. To make the variable available in the init block use:
init
a:string = ""
if true
a = "test"
print a
if true
a = "another"
print a
This shows the variable is accessible in the block it is declared and any sub-blocks.
The advantage of not having clashing names is clearer when applied to functions:
init
first_example_function()
second_example_function()
def first_example_function()
first_value:int = 1
second_value:int = 2
answer:int = first_value + second_value
print "%i + %i = %i", first_value, second_value, answer
def second_example_function()
first_value:int = 100
second_value:int = 200
answer:int = first_value * second_value
print "%i * %i = %i", first_value, second_value, answer
Here the working variables, such as answer, do not clash. This makes it easier to write a block of code because no checks need to be made by the programmer that the identifier has already been used.
Global Scope
Identifiers that are available in all blocks of the program, the global scope, should generally be avoided because they make it hard to test code. Writeable global identifiers introduce uncertainty into the program because it becomes unclear where the values are altered.
Namespaces
namespace MyNameSpace blah...
everything within the indent of this statement is in the namespace "MyNameSpace" 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.
To use a namespace, use the uses statement:
uses Gtk
Access Modifiers
Identifiers within objects can be marked as private or public. A private identifier is only accessible within the block scope it is identified in the object. A public identifier can also be accessed within the block scope the object was created.
Scope Based Resource Management
The final block of an object is called when the object goes out of scope. This can be used to release any resources claimed by the object.
Identifiers and Type
Function Signatures (Delegates)
delegate DelegateType(a:int)
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:
[indent=4] delegate DelegateType (a : int) : bool def f1 (a:int) : bool print "testing delegate value %d", a return true def f2 (d:DelegateType, a:int) var c = d (a) // to execute it init 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.
Delegates may also be created locally. A member method can also be assigned to a delegate. Here is an example of such usage:
class Test : Object data : int = 5 def method (a:int) print "%d %d", a, data delegate DelegateType (a : int) init var t = new Test() d : DelegateType = t.method d(1)
Using a delegate within a class is quite similar to its usage in a namespace. It is important, however, to utilize the constructor to avoid "assertion self != null failed" run-time errors. An example of delegate usage within a class:
class Test:Object delegate DelegateType (a : int) data : int = 5 d : DelegateType construct() self.d = method def method( b:int ) print "%d %d", b, data def run ( c:int ) d( c ) init var t = new Test() t.run( 1 )
Classes
Classes allow new data types to be defined.
When a class is instantiated it is called an object.
Interfaces
A class in Genie 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.
interface Test
prop abstract data:int
def abstract fn ()
This code describes an interface Test which contains two members. data is a property, as described above, except that it is declared abstract. Genie will therefore not implement this property, but instead require that classes implementing this interface have a property called data 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 fn is a method and as above any class implementing this interface must also have a method called fn with the same parameterss and return value.
A possible implementation of this interface is:
init
var f = new Foo()
f.fn()
interface Test
prop abstract data:int
def abstract fn()
class Foo:Object implements Test
prop data:int
def fn()
print "fn"
Interfaces in Genie may also inherit from other interfaces, for example it may be desirable to say that a "List" interface is also a "Collection" interface, and so the first should inherit from the second. The syntax for this is exactly the same as for describing interface implementation in classes:
interface List : Collection
However this definition of "List" may not now be implemented in a class without "Collection" also being implemented, and so Genie enforces the following style of declaration for a class wishing to implement "List", where all implemented interfaces must be described:
class ListCLass : Object implements Collection, List
Type Inference
Genie 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.
var i = 3 var a = "happy" b = "sad" c = "ambivalent" for var I = 1 to 10 print "looping"
var can be used for type inferencing in both single lines and blocks as well as for statement initializers.
Parameters of Type (Generics)
Genie 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, Genie 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 Genie, 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 - Genie'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 and create two instances of different types
[indent=4] init var string_wrapper = new Wrapper of string string_wrapper.set_data ("Hello World") var s = string_wrapper.get_data () print s var int_wrapper = new Wrapper of int int_wrapper.set_data (6) var data = int_wrapper.get_data () print "test int is %d", data var test_wrapper = new Wrapper of TestClass var test = new TestClass test.accept_object_wrapper (test_wrapper) class Wrapper of G : Object _data : G def set_data (data : G) _data = data; def get_data () : G return _data class TestClass : Object def accept_object_wrapper (w : Wrapper of Object) print "accepted object"
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 Genie there is no restriction on what type may be used in a generic). The above example shows one instance of type string and the other of type int.
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 Genie knows what sort of objects are in each wrapper instance, and therefore can do this work for you.
The fact that Genie does not create multiple classes out of your generic definition means that you can code a method like accept_object_wrapper above. 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 Object instance.
Functions
Defining a Named Function
A block of Genie code can be labelled with an identifier. The identifier can then be used to call the block at other points in the program without needing to copy the original code. In Genie a function definition starts with the def keyword, followed by some optional function definition modifiers, and then the identifier:
def example_function()
print( "example_function has been called" )
In the above example there is no definition modifier and the identifier for the function is example_function. The body of the function simply prints a sentence.
To call a function from another part of your program place parentheses, (), after the function identifier:
init
example_function()
def example_function()
print( "example_function has been called" )
This example will output:
example_function has been called
Later in the tutorial we will see that values can be passed to a function by placing them between the parentheses.
A function can also be assigned to a variable. Genie is a strongly typed language and a type must be given to the variable. Types for functions are declared with the delegate keyword. The example_function above does not return a value or accept any values when it is called. So its type is simply:
delegate FunctionExample()
The type in this example is called FunctionExample.
In the following example the FunctionExample type is used to declare the call_me variable. First the example_function is assigned to call_me. Note that example_function does not have parentheses after it. This means it is not the result of the function itelf that is assigned to the variable, but the identifier of the function. Effectively there are now two names for this function within the scope of the init block. The second identifier is call_me and like the example_function identifier it can be called by placing parentheses after its name:
init
example_function()
call_me:FunctionExample = example_function
call_me()
call_me = example_procedure
call_me()
delegate FunctionExample()
def example_function()
print( "example_function called" )
def example_procedure()
print( "example_procedure called" )
This example also re-assigns call_me with a second function example_procedure. So when call_me is invoked for a second time it produces different output. This is because it identifies a different function.
Parameters
A function in Genie 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
Heres an example:
[indent=4] init var a = 1 b = 2 c = 3 Foo.bar (a, out b, ref c) print "a=%d, b=%d, c=%d", a,b,c class Foo : Object def static bar (a:int, out b: int, ref c: int) a = 10 b = 20 c = 30
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.
'try...except' Blocks
GLib has a system for managing runtime exceptions called GError. Genie 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:
def fn (s:string) raises IOError
2) Throwing the error when appropriate:
if not check_file (s) raise new IOError.FILE_NOT_FOUND ("Requested file could not be found.")
3) Catching the error from the calling code:
try fn("home/jamie/test") except ex : IOError print "Error: %s", 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:
exception IOError FILE_NOT_FOUND FILE_NO_READ_PERMISSION FILE_IS_LOCKED
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:
[indent=4] exception ErrorType1 CODE_1A exception ErrorType2 CODE_2A init try Test.catcher () except ex:ErrorType1 print ex.message + " was caught" class Test:Object def static thrower () raises ErrorType1, ErrorType2 raise new ErrorType2.CODE_2A( "Error CODE 2A was raised" ) def static catcher () raises ErrorType1 try thrower () except ex:ErrorType2 print ex.message finally print "all exceptions handled"
This example has two exceptions, 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.
Objects
Genie is an Object Orientated language, although it is quite possible to write code without objects. In most cases you will want to use objects to build your application as they have a number of benefits.
Objects may contain fields, properties, methods and events all of which can be made publicly available to other code or can be private and internal to the object. An object's class can also inherit from another class.
All members of an object are publicly accessible, unless they are defined in the class with an identifier starting with an underscore or use the private keyword modifier.
An object is instantiated using the new operator before the object's class, e.g.:
init var foo = new Foo()
Fields
Fields are variables declared in the scope of the class.
Constructors
A construct block is used to define a creation method at construction time when an object is being instantiated via the new operator. A class can have many creation methods with either different names or different parameters.
EG
[indent=4] init /* different creation methods can be specified (i.e. those defined with "construct" keyword) where they take different params */ var foobar = new Foo( 10 ) /* this creation method is same as above but is named differently */ var foobar2 = new Foo.with_bar( 10 ) class Foo:Object construct( b:int ) a = b construct with_bar( bar:int ) a = bar prop a:int init print "foo is intitialized" final print "foo is being destroyed"
An init block is used to wite code that pertains to the initialization of the class - only one of these may be present in any class declaration. An init block declared outside of a class is equivalent to a main function in C and is used to perform initialization of the entire application. init blocks do not have any parameters.
Destructors
A final block is used to perform any finalization of the class when an object instance is destroyed. Final blocks do not take parameters
Properties
Genie classes can also have properties which are identical to GObject properties
Within a class, properties can be defined in various ways, including:
class Foo:Object prop name:string prop readonly count:int [Description(nick="output property", blurb="This is the output property of the Foo class")] prop output:string get return "output" set _name = value
In the simplest case, the name property is declared without any get/set methods so Genie automatically creates a private field called _name (it always generates a field with the same name as the property but with an underscore prepended) and performs automatic get/set operations for you
Properties that can only be read and never written must use the readonly keyword as in the count property above.
Properties, as in the last case, can of course have user defined get and set definitions as well as GObject short and long descriptions (via the attribute).
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"):
var t = new T() t.name = "my name"
Methods
Methods are actions on an object.
Methods always start with the keyword def followed by optional modifiers (private, abstract, inline, extern, virtual, override), the name of the method, a list of method parameters (if any) and lastly, if it returns a value, the type of the return value.
Here's an example
[indent=4] init var f = new Foo () print f.bar ("World") class Foo def static bar( name:string):string return "Hello " + name
The above defines a method called bar which takes a string parameter called name and returns a string.
All methods, properties and events can also take modifiers to define further options. Modifiers are always placed between the def keyword and the method name. A full explanation of these modifiers are as follows:
private - indicates that this method/property/event is not accessible from outside the defining class. It is not necessary to include this modifier if its name starts with an underscore as anything that starts with that is always deemed private
extern - indicates that this method is defined outside of genie/vala sources and is often implemented in C. This handy feature allows methods to be defined in C (or assembler) where necessary - c files to be included in the final executable have to be passed to valac in order for this to work.
inline - hints to the compiler that it should inline this method to improve performance. This is useful for very small methods
static - indicates a method that can be run without needing an instance of the class. Static methods never change the state or properties of a class instance as a result of this.
Events (Signals)
An event is similar to a method, but allows for multiple methods or functions to be called from a single source. It is an example of the observer pattern and uses GObject's signals for its underlying implementation.
The event keyword defines a member of a class as an event source and appears similar to a method with no body. Event handlers can then be added to the event using the connect() method:
init
var a = new Test()
a.test_event.connect( first_handler )
a.test_event.connect( second_handler )
a.test_event()
class Test
event test_event()
def first_handler()
print( "first_handler called" )
def second_handler()
print( "second_handler called" )
We emit the event by calling it like a method on the class.
Events in Genie are the same as signals in Vala and are commonly used in the GTK+ graphical toolkit.
Sub-Typing
Sub-typing in Genie is pretty much the same as in other OO languages. Multiple inheritance is not supported and so a class in Genie can only descend directly from one other class. Although its super-type can descend from another class. A class can also implement many interfaces.
abstract - indicates that the method body is not defined in the current class but any subclass must implement this method. This is very useful when defining interfaces which classes can implement
virtual - indicates that this method may be overriden in subclasses
override - indicates to override the super class virtual method of the same name and replace it with the new definition. If no method with the virtual modifier exists in the superclass then the compiler will signal an error.
Inheriting from 'Object'
A simple example:
class Foo:Object /* property declaration */ prop p1:int /* method declaration */ def bar( name:string ) print "hello %s p1 is %d", name, p1 /* method with return value */ def bar2():string return "bar2 method was called"
The above example shows that class Foo is a subclass of object (which means it inherits all the features of GLib's GObject such as interfaces and events).
Special Operators
You can check if a object is of a given Object type by using the isa operator:
init var o = new list of string if o isa Object print "a list of string is an object" if o isa list of Object print "a list of string is a list of Object" if o isa Gee.Iterable print "a list of string implements Iterable"
Collections
Genie has a number of collection types built in including arrays, lists and dicts.
Gee provides the support for list and dict types in Genie so if you make use of these you will need to have libgee installed (as will anyone who wants to run your compiled programs). If you dont make use of list and dicts then you do not need this library.
The choice of collection type will depend on the underlying algorithm and how that affects performance for the application. This is mainly a factor for larger datasets and includes:
- insertion time
- retrieval time
- sorting time
GLib's GenericArray
GenericArray is a binding to GLib's GPtrArray (Pointer Arrays). The GLib Reference Manual description for GPtrArray advises:
Pointer Arrays are similar to Arrays but are used only for storing pointers. If you remove elements from the array, elements at the end of the array are moved into the space previously occupied by the removed element. This means that you should not rely on the index of particular elements remaining the same. You should also be careful when deleting elements while iterating over the array.
GenericArray works well with reference types because the reference to the type is implemented as a C pointer. The data in the array can be iterated over with Genie's for loop. Note that the data field of GenericArray is used to get access to the underlying array:
init
var collection = new GenericArray of Example()
collection.add( new Example( "First" ))
collection.add( new Example( "Second" ))
for item in collection.data
item.show()
class Example
_value:string = ""
construct( value:string )
_value = value
def show()
print( _value )
Basic types like int and double will need to be made in to reference types by "boxing". This means adding a ? to the type, for example int?:
init
var collection = new GenericArray of int?()
collection.add( 1 )
collection.add( 2 )
for item in collection.data
print( "%i", item )
For more details on GenericArray see Valadoc.org
GLib's HashTable
GLib's HashTable maps to D-Bus's dictionary type. So it is useful for writing D-Bus code in Genie. A D-Bus dictionary of type a{sv} would be created in Genie as:
new HashTable of (string,Variant)(str_hash, str_equal)
This has a string as a key and a GVariant as a value. The HashMap needs to be supplied with a hashing function and equality comparison function for the key's data type. In this example GLib has the functions str_hash and str_equal for the string data type.
For more details on HashArray see Valadoc.org
Lists (Gee's ArrayList)
Lists are ordered collections of items, accesible by numeric index. They can grow and shrink automatically as items are added/removed. In essence a list is really a dynamic array.
Lists are generic in nature and use the same syntax as other generics. A new list is therefore created in the same way:
var l = new list of string
the above creates a string list but as with generics all other types are supported too. Elements in a list can be accessed and changed just like it was an array. As lists are also iterables 'for..in' can be used to iterate over them too.
[indent=4] init /* test lists */ var l = new list of string l.add ("Genie") l.add ("Rocks") l.add ("The") l.add ("World") for s in l print s print " " l[2] = "My" for s in l print s print " "
Dicts (Gee's HashMap)
Dicts (also known as dictionaries) are an unordered collection of items accesible by index of arbitry type.
They can be used as lookup tables and to quickly map values of one types to another values of the same or another type
In other languages dicts may be known as hash maps or hash tables.
dicts typically consist of key/value pairs and when creating a new one you need to specify the key and value types :
var d = new dict of string,string
The above example creates a dict whose key is a string and whose value is also a string
The types can be different of course. Say we want to implement a phone book then we would have the key as a string (name) and the phone number as an int. We would create this using:
var d = new dict of string,int
Dicts, like lists, can be accessed by key index and its keys and values can also be iterated over using the standard for..in statement. You can add new entires by using the add method or more easily by using d[key] = value
Here is a full example:
[indent=4] init /* test dicts */ var d = new dict of string,string /* add or change entries with following */ d["Genie"] = "Great" d["Vala"] = "Rocks" /* access entries using d[key] */ for var s in d.keys print "%s => %s", s, d[s]
Iterators
A for loop can be used to iterate over a class's collection by adding get() and next() methods to the class. See Methods with Syntax Support in the Vala tutorial.
Searches
The in operator can be used to search if an item is in a class's collection by adding a contains() method to the class. See Methods with Syntax Support in the Vala tutorial.
Interfacing with a C Library
Nullable Types
By default, Genie 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:
/* allows null to be passed as param and allows null to be returned */ def fn_allow_nulls (param : string?) : string? return param /* attempting to pass null as param will lead to that function aborting. Likewise it may not return a null value */ def fn_no_null (param : string) : string return 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.
Arrays
An array is an ordered collection of items of a fixed size. You cannot change the size of an array so additional elements cannot be added or removed (although existing elements can have their value changed)
An array is created by using "array of type name" followed by the fixed size of the array e.g. var a = new array of int[10] to create an array of 10 integers. The length of such an array can be obtained by the length member variable e.g. var count = a.length.
Arrays can take initilializers too, in which case theres no need to specify a length :
tokens : array of string = {"This", "Is", "Genie"}
Arrays can be constant too:
const int_array : array of int = {1, 2, 3}
Individual elements in an array can be accessed by their position index. They can also be iterated over using the standard for..in statement. Heres an example demonstrating this:
[indent=4] const int_array : array of int = {1, 2, 3} init tokens : array of string = {"This", "Is", "Genie"} var sa = new array of string[3] var i = 0 for s in tokens print s sa[i] = s i++ sa[2] = "Cool" for s in sa print s for i in int_array print "number %d", i
You can have arrays of any type including nested types. Arrays of structs are also possible:
[indent=4] init var f = new Foo () try var opt_context = new OptionContext ("- Test array of structs") opt_context.set_help_enabled (true) opt_context.add_main_entries (f.options, null) opt_context.parse (ref args) except e:OptionError stdout.printf ("%s\n", e.message) stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0]) class Foo : Object [NoArrayLength ()] test_directories : static array of string const options : array of OptionEntry = {{ "testarg", 0, 0, OptionArg.FILENAME_ARRAY, ref test_directories, "test DIRECTORY", "DIRECTORY..." }, {null}}
Structs
A struct type, i.e. a compound value type. A Genie struct may have methods in a limited way and may also have private data.
struct StructName a : string i : int
The example struct StructName can be initialized as follows:
var mystruct = StructName() var mystruct = StructName() {"mystring", 1} var mystruct = StructName() { a = "mystring", i = 1 }
Callbacks
A Genie function can be passed as a callback to a library function. The identifier of the Genie callback function needs to be passed as an argument to the library function:
init
CLibrary.library_function( my_callback )
def my_callback( example:string )
print example
Concurrency
Concurrency in a computer program is running multiple, independent tasks within a given time frame. Physically this can be completed on a single processor, but can also be completed on multiple processors in parallel. Genie makes use of GLib's concurrency functions to implement this.
GLib's Event Loop - GMainContext
GMainContext is usually wrapped in a higher level function, for example GLib's MainLoop or Gtk's MainLoop.
GLib's event loop is unrelated to Genie's event keyword. The event keyword relates to synchronous communication between objects.
An Event Source Example
GLib's Timeout can be used to implement a timer as an example of an event source.
Another event source is a network socket when it receives data.
Threads
Sharing Data Between Threads
GLib's ThreadPool
Asynchronous Methods
Asynchronous methods are methods whose execution can be paused and resumed under the control of the programmer. They are often used in the main thread of an application where a method needs to wait for an external slow task to complete, but must not stop other processing from happening. (For example, one slow operation must not freeze the whole GUI). When the method has to wait, it gives control of the CPU back to its caller (i.e. it yields), but it arranges to be called back to resume execution when data becomes ready. External slow tasks that async methods might wait for include: waiting for data from a remote server, or waiting for calculations in another thread to complete, or waiting for data to load from a disk drive.
Asynchronous methods are normally used with a GLib main loop running, because idle callbacks are used to handle some of the internal callbacks. However under certain conditions async may be used without the GLib main loop, for example if the async methods always yield and Idle.add() is never used.
Asynchronous methods are designed for interleaving the processing of many different long-lived operations within a single thread. They do not by themselves spread the load out over different threads. However, an async method may be used to control a background thread and to wait for it to complete, or to queue operations for a background thread to process.
Async methods in Genie use the GIO library to handle the callbacks, so must be built with the --pkg=gio-2.0 option.
An asynchronous method is defined with the async keyword. For example:
def async display_jpeg(fname : string) // Some code to load JPEG in a background thread here and display it when loaded // ...
or
def async fetch_webpage(url : string, out text : string) : int raises IOError // Some code to fetch a webpage asynchronously and when ready return the // HTTP status code and put the page contents in 'text' text = result return status
The method may take arguments and return a value like any other method. It may use a yield statement at any time to give control of the CPU back to its caller.
An async method is called like that:
display_jpeg("test.jpg")
or
display_jpeg.begin("test.jpg")
These forms are equivalent and start the async method with the given arguments.
When an asynchronous method starts running, it takes control of the CPU until it reaches its first yield statement, at which point it returns to the caller. When the method is resumed, it continues execution immediately after that yield statement. There are several common ways to use yield:
This form gives up control, but arranges for the GLib main loop to resume the method when there are no more events to process:
Idle.add(fetch_webpage.callback) yield
This form gives up control, and stores the callback details for some other code to use to resume the method's execution:
callback : SourceFunc = fetch_webpage.callback yield
Some code elsewhere must now call the stored SourceFunc in order for the method to be resumed. This could be done by scheduling the GLib main loop to run it:
Idle.add((owned) callback)
or alternatively a direct call may be made if the caller is running in the main thread:
callback()
If the direct call above is used, then the resumed asynchronous method takes control of the CPU immediately and runs until its next yield before returning to the code that executed callback(). The Idle.add() method is useful if the callback must be made from a background thread, e.g. to resume the async method after completion of some background processing. (The (owned) cast is necessary to avoid a warning about copying delegates.)
The third common way of using yield is when calling another asynchronous method, for example:
yield display_jpeg(fname)
or
text : string var status = yield fetch_webpage(url, out text)
In both cases, the calling method gives up control of the CPU and does not resume until the called method completes. The yield statement automatically registers a callback with the called method to make sure that the caller resumes correctly. The automatic callback also collects the return value from the called method.
When this yield statement executes, control of the CPU first passes to the called method which runs until its first yield and then drops back to the calling method, which completes the yield statement itself, and then gives back control to its own caller.
Inter-Process Communication
GVariant
The Vala compiler contains a module to generate Gvariant. This is used in Genie by casting to Variant.
GVariant is used by GLib libraries for serialization and deserialization. For example in the D-Bus APIs, the gvariant_serialize_data () function of json-glib library and GSettings.
D-Bus
The [DBus] attribute makes using the gdbus implementation of D-Bus easy in Genie. See Vala/DBus, Vala D-Bus Client Examples and Vala D-Bus Examples.
Environment Variables
GLib has functions to access variables in a process's environment. These functions are cross platform and work on Windows and POSIX environments, such as Linux and macOS. They are bound in Vala within the GLib.Environment namespace. The following Genie code example lists all the variables in its environment. A string template is used to format the output:
init
var all_variables = Environment.list_variables()
for var item in all_variables
print( @"$(item) = $(Environment.get_variable(item))" )
The list_variables() function is bound in Vala to GLib's g_listenv function. The GLib documentation about Miscellaneous Utility Functions includes more details on g_listenv and related functions. The documentation also gives more details on cross platform support.
POSIX systems have the PWD environment variable set when the program is run from a shell. This variable contains the present working directory. Running the next Genie program in Linux, macOS or another POSIX system should show the directory it was run from:
init
print( @"Present working directory: $(Environment.get_variable( "PWD" ))" )
The POSIX documentation contains more information on environment variables.
The GLib function to retrieve a variables' value returns null if the variable is unknown. In Genie a null check would be needed. In this next example an empty string is used if there is no value:
init
var value = Environment.get_variable( "ENVIRONMENT_VARIABLE_THAT_DOES_NOT_EXIST" )
if value == null
value = ""
print( "Environment variable value: " + value )
Running External Programs
Use GLib.Subprocess. For low level control use GLib.SubprocessLauncher. These APIs are both part of GIO so you will need to compile with --pkg gio-2.0.
Pipes
Handling C/POSIX System Signals
User Interfaces
Command Line Interfaces
GLib's OptionContext for option parsing.
Text User Interfaces
Graphical User Interfaces
Genie has excellent bindings to Gtk+3. Including the [GtkTemplate] attribute for accessing windows and widgets from a GResource compiled into a progam.
Voice User Interfaces
Genie has excellent bindings to GStreamer. An interesting idea of using PocketSphinx for voice recognition could be implemented in Genie. See Using PocketSphinx with GStreamer and Python.
Attributes
An attributes is metadata that specifies alternative handling of the source below it.
Attributes may be used to:
- Change indentation from tabs to spaces
- Modify the generated C code (CCode)
Specify a variant type of structs (SimpleType, FloatingType, etc)
- Provide a description of an object
Attributes are placed in square brackets "[]" immediately before the symbol they modify. Multiple attributes applying to the same symbol can be separated by a comma;
[CCode (cname="dbCallbackStruct", cprefix="Foo__"), Compact] class Callback
With exception of the indent attribute, all attributes only apply to the following symbol and its children.
For a complete and current list of available attributes, see the Vala Manual.
Advanced Memory Management
There are broadly two types of data in Genie: 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.
Genie'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)
- string
The reference types are all types declared as a class, regardless of whether they are descended from GLib's Object. Genie 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.
Weak References
Genie'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 : Object prev : weak Node next : Node
This topic is explained in detail on this page (Vala page, but relevant for Genie, too): Vala's Memory Management Explained. Important note from the page is that at the moment "unowned" and "weak" can be used interchangeably. However, you should use weak only for breaking reference cycles and unowned only for ownership issues as described in the linked page.
Unowned References
Normally when creating an object in Genie 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 Genie'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 function defined to return a weak reference, e.g.:
class Test o : Object def get_unowned_ref () : unowned Object o = new Object() return o
When calling this function, in order to collect a reference to the returned object, you must expect to receive an unowned reference:
o : unowned Object = get_unowned_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_unowned_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 unowned, the ownership would pass to the calling code. The calling code is, however, expecting an unowned reference, which cannot receive the ownership.
If the calling code is written as
o : Object = get_unowned_ref();
Genie will try to either obtain a reference of or a duplicate of the instance the unowned reference pointing to.
In contrast to normal methods, properties always have unowned return value. That means you can't return a new object created within the get method. That also means, you can't use an owned return value from a method call. The somewhat irritating fact is because of that a property value is owned by the object that HAS this property. A call to obtain this property value should not steal or reproduce (by duplicating, or increasing the reference count of) the value from the object side.
On the other hand, this is perfectly fine
prop property : string get return getter_method() // GOOD: getter_method returns an unowned value def getter_method() : unowned string return "sometext" // Don't be alarmed that the text is not assigned to any strong variable. // Literal strings in Genie are always owned by the program module itself, // and exist as long as the module is in memory
The unowned modifier can be used to make automatic property's storage unowned.
_property : unowned Object prop property : Object get return _property
The keyword owned can be used to specifically ask a property to return an owned reference of the value, therefore causing the property value be reproduced in the object side. Think twice before adding the owned keyword. Is it a property or simply a get_xxx method? There may also be problems in your design.
prop property : Object owned get return new Object
Unowned 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. However, in general they should not be widely used in the programs unless you know what you are doing.
Ownership Transfer
The keyword '(owned)', like in Vala, is used to transfer ownership.
As a prefix 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.
var s = "hello" /* s will be null when owner is transferred */ t : string = (owned) s print t print s
This means that s will be set to null and t inherits the reference/ownership of s.
Pointers
Pointers are Genie's way of allowing manual memory management. Normally when you create an instance of a type you receive a reference to it, and Genie 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 you must add the * suffix to the type declaration :
o : object* = 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;
Using Genie and Vala together
This is actually very easy as in it seems to just work as you'd expect (I'm not an expert so maybe someone could add in any oddities that may be faced when doing this.)
Here's a quick example:
// Vala: test_vg.vala public class TestVala { public void vala_print(string str) { stdout.printf("%s", str); } } void main() { test("Hello vala+genie"); var test_class = new TestGenie(); test_class.vala_print("call to inherited parent member from Vala\n"); test_class.genie_print("call to member from Genie\n"); }
[indent=4] // Genie: test_gv.gs def test (str : string) print "%s", str class TestGenie : TestVala def genie_print(str : string) stdout.printf("GENIE: ") vala_print(str)
compile with:
$ valac -o test_vala-genie test_vg.vala test_gv.gs
Some notes:
- File names must differ even if they're in different paths.
More Resources
- Bindings:
List Of Bindings - a list of bindings maintained on this wiki
Valadoc - comprehensive documentation on bindings to C libraries, useful starting point for coding any project
Vala Extra Vapis Repository - Additional Vala bindings to C libraries, these bindings are not currently included in the main Vala distribution
Vala Bindings - starting point should you need to create your own bindings for a library (for advanced users)
Legacy Bindings - detailed guide for creating a binding where GObject Introspection is not available (for advanced users)
- GLib general-purpose utility library:
glib-2.0 - Valadoc documentation
GLib Reference Manual - Gnome Developer Documentation
GLib - Wikipedia entry
- Genie community:
Genie Tutorials, Blogs and Code Examples - a page listing links to numerous tutorials, articles, blogs, code examples and projects
Newest 'genie' Questions - StackOverflow questions and answers about Genie
vala-list -- Vala compiler development and discussion - the Vala mailing list where Genie questions can be asked too
Genie Programming Language - subreddit for Genie at Reddit news site
Developing Genie - a useful starting point if you want to improve the Genie scanner and parser, includes communication channels for developers and a detailed guide on downloading and compiling the Vala source code