Cent icon

Language Overview

If you haven't installed Cent yet, refer to the Installation Guide or use Cent Playground to run code right in your browser.

Introduction

This tutorial assumes you are familiar with basic programming concepts and have used another programming language before.

Hello, world!

We'll write a simple program that prints Hello, world! to the screen. Create a new file named main.cn with the following content:

with std::io;

fn main() {
    io::println("Hello, world!");
}

Now, run these commands to compile and execute the program:

$ centc main.cn
$ ./main
Hello, world!

You can also use the --run option to automatically run the compiled executable:

$ centc main.cn --run
Hello, world!

Variables

The let statement creates an immutable variable.

let language = "Cent";

To create a mutable variable, use the mut keyword.

mut score = 0;
score = 10;

A variable's type can be specified explicitly:

mut score: i32 = 0;

Variables are zero-initialized if no value is given.

mut score: i32;

Cent is statically typed, so you cannot change the type of a variable.

mut score = 10;
score = 4.5; // invalid!

You can, however, shadow a variable, meaning to create a new variable with the same name:

mut score = 10;
mut score = 4.5;
score = 7.3;
mut score = 10;
let score = score; // score is no longer mutable
score = 7; // invalid!

Comments

You can use comments to explain certain parts of your code. Comments start with //.

let variable = 10; // this is a variable

Constants

To create a constant, use the const keyword. Constants are computed at compile time.

const PI = 3.14;
const GOLDEN_RATIO = 1.618;
const SECONDS_IN_A_DAY = 60 * 60 * 24;

Data types

Integer types

Integer types start with either i or u. Integer types starting with u are unsigned. After that, the size in bits is specified:

i8 i16 i32 i64 // signed
u8 u16 u32 u64 // unsigned
let a: u64 = 3;
let b: i8 = -128;

There are special usize and isize types. They have the size of the pointer type and are usually used for indexing.

Floating-point types

Floating-point types are used to store numbers with decimal points. In Cent, there are two such types: f32 and f64.

let a: f32 = 3.5;
let b: f64 = 1.2345678;

The bool type

A bool value is either true or false:

mut raining: bool = false;
raining = true;

The rune type

The rune type represents a Unicode code point and is 4 bytes long.

let fire: rune = 'πŸ”₯';

Array types

Array types are created by using the [N]T syntax. Arrays hold multiple values of the same type:

let data = [4]u8{0xff, 0xff, 0xff, 0x0};
let data = [_]u8{0xff, 0xff, 0xff, 0x0}; // array length can be deduced

Arrays can be of variable length.

Variable-length arrays are allocated on the stack, which has a limited size. If the array size is too large, this can result in a stack overflow.
mut n: usize = 16;
n = 1024;
mut data: [n]u8;

Slice types

Slice types are created by using the []T syntax. Slices represent a view into a sequence of elements. They consist of a pointer and a length.

mut data: [1024]u8;
let slice: []u8 = data;

let len = slice.len;
let ptr = &slice[0];

Slices can be mutable:

mut data: [1024]u8;
let slice: []mut u8 = data;

slice[10] = 42;

Strings?

In Cent, strings are just arrays of bytes. Strings are not null-terminated.

let language: [4]u8 = "Cent";
let language = [_]u8{'C' as u8, 'e' as u8, 'n' as u8, 't' as u8};

let null_terminated = "Hello, world!\0";

Optional types

Optional values can either be null or contain a value. To create an optional type, use the ?T syntax:

mut optional: ?i32 = 32; // optional != null

optional = 42;
optional = null; // optional == null

To access the contained value without any checks, use the .! syntax:

mut optional: ?i32 = 32;
let value = optional.!;

Pointer types

Pointers reference a value in memory. To create a pointer type, use the *T syntax.

mut x = 42;
let ptr: *i32 = &x; // *ptr = 42
x = 422; // *ptr = 422

Pointers can be mutable:

mut x = 42;
let ptr: *mut i32 = &x;
*ptr = 422; // x = 422
In Cent, pointers can't be null. If you need a nullable pointer, use an optional pointer type. An optional pointer has the same size as a regular pointer.

Tuple types

Tuple types are created by using the (T1, T2, T3, ...) syntax. Tuples hold multiple values of different types:

mut data: (i32, f32, bool, [6]u8) = (10, 42.42, true, "Hello!");
data.0 += 32; // data.0 = 42
data.1 = data.0; // data.1 = 42

with statements

You can use the with statement to import an external module.

with std::io;
with std::fs;

with std::posix as os; // import under a different name

Functions

Functions are defined by using the fn keyword. The main function is the entry point of the program. Functions can be used before they're defined.

with std::io;

fn main() {
    hello_world();
}

fn hello_world() {
    io::println("Hello, world!");
}

You can specify the return type after the parentheses. If no return type is specified, the function is assumed to return nothing. To return a value from a function, use the return statement:

fn get_magic_number() i32 {
    return 42;
}

Functions can take parameters.

fn main() {
    let a = add(3, 4); // a = 7
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}

Default parameters

You can define default parameters. If arguments aren't provided, the default values are used.

fn main() {
    let ten = add(3, 7);
    let nine = add(3, 3, 3);
    let one = add(1, -1, 1, 0);
}

fn add(a: i32, b: i32, c: i32 = 0, d: i32 = 0) i32 {
    return a + b + c + d;
}

Control flow

if statements

Use the if statement to branch your code depending on a condition.

with std::io;

fn main() {
    print_is_even(3); // x is odd
    print_is_even(4); // x is even
}

fn print_is_even(x: i32) {
    if x % 2 == 0 {
        io::println("x is even!");
    } else {
        io::println("x is odd!");
    }
}

You can use else if to check additional conditions.

with std::io;

fn greet(hour: u8) {
    if hour < 12 {
        io::println("Good morning!");
    } else if hour < 18 {
        io::println("Hello!");
    } else {
        io::println("Good evening!");
    }
}

switch statements

The switch statement allows you to compare a value against several possible cases:

with std::io;

fn day_of_week(day: u8) {
    switch day {
        1 { io::println("Monday"); }
        2 { io::println("Tuesday"); }
        3 { io::println("Wednesday"); }
        4 { io::println("Thursday"); }
        5 { io::println("Friday"); }
        6 { io::println("Saturday"); }
        7 { io::println("Sunday"); }
        else { io::println("Invalid day of week!"); }
    }
}

You can match multiple values in a single case.

with std::io;

fn is_weekend(day: u8) {
    switch day {
        1, 2, 3, 4, 5 { io::println("Weekday"); }
        6, 7 { io::println("Weekend!"); }
    }
}

while loops

A while loop will run as long as the condition is true.

with std::io;

fn main() {
    mut i = 0;

    while i < 10 {
        i += 1;
    }

    io::print_int(i); // 10
    io::print_rune('\n');
}

You can use while true to create an infinite loop. To exit a loop, use the break keyword.

with std::io;

fn main() {
    mut i = 0;

    while true {
        if i == 100 {
            break;
        }

        i += 2;
    }

    io::print_int(i); // 100
    io::print_rune('\n');
}

To skip an iteration, use the continue keyword:

with std::io;

fn main() {
    mut i = 0;
    mut sum = 0;

    while i < 10 {
        i += 1;

        if i % 2 == 0 {
            continue;
        }

        sum += i;
    }

    io::print_int(sum); // 25
    io::print_rune('\n');
}

for loops

for loops allow you to iterate through a range or a sequence.

Exclusive ranges are created by using the x..y syntax:

with std::io;

fn main() {
    for i in 1..10 {
        io::print_int(i);
        io::print_rune('\n');
    }
}

To create an inclusive range, use the x..=y syntax:

with std::io;

fn main() {
    for i in 1..=10 {
        io::print_int(i);
        io::print_rune('\n');
    }
}

Literals

Numeric literals

Integers starting with the 0x prefix represent a hexadecimal integer literal. The 0o and 0b prefixes represent octal and binary literals, respectively.

let hex = 0xff; // hex = 255
let oct = 0o777; // oct = 511
let bin = 0b101010; // bin = 42

You can insert underscores for better readability:

let big_number = 1_000_000_000; // big_number = 1000000000

String literals

String literals generate a UTF-8 encoded sequence of bytes.

let string = "𝒰𝓃𝒾𝒸ℴ𝒹ℯ πŸ’Ž";

Expressions

Binary expressions

Operator Precedence
* / % 1
+ - 2
<< >> 3
< > == != >= <= 4
& 5
^ 6
| 7
&& 8
|| 9
?? 10
let a = 2 + 3 * 6; // a = 20

Unary expressions

let a = 5;
let b = !true; // b = false
mut c = -a; // c = -5
let p = &c;
*p = -c; // c = 5