Getting Started

Overview

Rivet's goal is to be a very powerful programming language and at the same time easy to use, with a syntax that is the result of mixing Go + Zig + C#, and by other languages such as Python, Lua, TypeScript, D, etc.

Rivet uses the C programming language as its main backend.

Before continuing, I assume you know how to use a console, otherwise you can read this tutorial: The Linux command line for beginners.

Run the compiler

Dependencies

  • The compiler requires Python 3.

  • The Rivet compiler currently generates C code, so a C compiler, which supports C11, is required to generate executables. Over time the compiler will add support for generating binaries directly without the need for a C compiler.

The compiler has been tested on linux.

Just execute python3 rivetc some_file.ri.

You can see all available compiler options by using the -h/--help flag.

python3 rivetc -h

Hello World!

Let's start with the typical Hello World!:

We create a file called hello_world.ri with the following content:

import std/console;

func main() {
    console.writeln("Hello World!");
}

Then we compile that file:

$ python3 rivetc hello_world.ri

We'll get an executable called hello_world as output, so we run it:

$ ./hello_world

We should see this output:

Hello World!

Excellent! You have compiled your first program in Rivet!

Editor/IDE support

Code Structure

Comments

// This is a single line comment.
/*
This is a multiline comment.
*/

You can use comments to make reminders, notes, or similar things in your code.

Entry point

In Rivet, the entry point of a program is a function named main.

func main() {
    // code goes here
}

Top-level declarations

On the top level only declarations are allowed.

import { import_list, ... } from module;

const FOO: int32 := 0;

static foo: int32 := 0;

alias Foo := Baz;

enum Foo { /* ... */ }

trait Foo { /* ... */ }

struct Foo { /* ... */ }

extend Foo { /* ... */ }

func foo() { /* ... */ }

test "foo" { /* ... */ }

Functions

Functions contain a series of arguments, a return type, and a body with multiple statements.

The way to declare functions in Rivet is as follows:

func <name>(<args>) [-> return_type] {
	...
}

For example:

func add(a: int32, b: int32) -> int32 {
	return a + b;
}

add returns the result of adding the arguments a and b.

Functions can have 0 arguments.

// `f1` returns a simple numeric value of type `int32`.
func f1() -> int32 {
	return 0;
}

// `f2` takes an argument of type `int32` and prints it to the console.
func f2(a: int32) {
	println("a: {}", a);
}

// `f3` takes no arguments and returns void.
func f3() { }

A function body is made up of 1 or more statements and can be empty.

func x() {
	/* empty body */
}

func y() {
	my_var := 1; // statement
}

Arguments

The arguments are declared as follows: <name>: <type> [:= default_value], for example: arg1: int32, arg2: bool := false.

The arguments are immutable.

They can also have default values, this bypasses the need to pass the argument each time the function is called: arg1: int32 = 5.

So, if we have a function called f5 with a default value argument, we can call it in 3 ways:

func f5(arg1: int32 := 5) {
	println("arg1: {}", arg1);
}

f5(); // use the default value `5`
f5(100); // will print 100 instead of 5 to the console

// this uses a feature called `named argument`, which allows an optional
// argument to be given a value by its name in any order
f5(arg1: 500); // will print 500 instead of 5 to the console

Statements

Each statement must end with a semicolon.

Variables

Variables are like boxes that contain values.

Variables are declared as follows: [mut] <name>[: <type>] := <value>;. Example:

x: int32 := 1;

We have created a variable called x, which contains the value 1 and is of type int32.

The type of the variable can be omitted.

x := 1; // via inference, the compiler knows that `x` is an `int32`.

By default, all variables are immutable, that is, their values do not change. To change the value of a variable you have to declare it with mut.

mut x := 1;
x = 2; // this is valid

y := 1;
y = 2; // error: `y` is immutable

Multiple values can be assigned on a single line via tuple-destructuring, example:

(a, b, c) := (1, 2, 3);
(c: int32, d: int32, e: int32) := (4, 5, 6);
(f, g, h) := tuple_fn();

// this is a short form for:

a := 1;
b := 2;
c := 3;

c: int32 := 4;
d: int32 := 5;
e: int32 := 6;

tmp_tuple_fn := tuple_fn();
f := tmp_tuple_fn.0;
g := tmp_tuple_fn.1;
h := tmp_tuple_fn.2;