Basics

Printing

The first thing you should know about Roto is how to make it print something. That is done with the print function, which takes a single String as input. Strings are delimited by double quotes (").

Roto
fn main() {
    print("Hello, World!");
}
Output
Hello, World!

Variables are created with let. In the example below, we create a variable called first_name that holds a string. A variable’s type is usually inferred (in this example, the fact that we are assigning a string literal to the variable implies that it is a string type), but you can be explicit about it by adding a type annotation. Roto’s type checker will then check that the variable has that type and produce an error if you try to use it in a way that is not compatible with the string type. Because it’s a string, we can join it to other strings using the + operator.

Roto
fn main() {
    // Create a variable with the inferred type String
    let first_name = "John";
    print("Hello, " + first_name);

    // Create a variable with a type annotation
    let last_name: String = "Doe";
    print("Bye, " + first_name + " " + last_name + "!");
}
Output
Hello, John
Bye, John Doe!

A more convenient way to build complex strings is with string formatting. A string prefixed with f will act as a format string (or f-string) where code in curly braces ({}) will be evaluated as a Roto expression and inserted at that location in the string. Usefully, this includes the ability to perform a conversion from the variable’s type to a string (assuming the type knows how to do that), so we don’t have to worry about that when using f-strings.

Roto
fn main() {
    let first_name = "John";
    let last_name = "Doe";
    print(f"Hello, {first_name} {last_name}!");
}
Output
Hello, John Doe!

We are not limited to using names of variables in f-strings, we can also use more complex expressions. For instance, we can convert the name to uppercase before printing it.

Roto
fn main() {
    let name = "John";
    print(f"Hello, {name.to_uppercase()}");
}
Output
Hello, JOHN

See also

Strings in the language reference.

The String type.

The print function.

Simple Calculations

Of course, Roto doesn’t just have strings. The next thing we should introduce are Roto’s numeric types. The simplest numeric types in Roto are integers. There are two main kinds of integers: signed and unsigned. Since integers are used very often, we use short names for them. Types of signed integers are prefixed with i followed by their size in bits: i8, i16, i32 and i64. Unsigned integers are the same but with u instead of i. Signed integers can express negative values, but have half the size of unsigned integers. So for example, i8 can express values from -128 to 127, while u8 can express values from 0 to 255.

The integers support many operators that you might expect, such as + for addition, - for subtraction, * for multiplication, / for division and % for remainder. These operators follow the conventional rules for precedence.

While we can’t print integers directly, we can use them in f-strings.

Roto
fn main() {
    // We can specify the type to get a specific integer type
    let a: u8 = 2 + 1;
    print(f"2 + 1 = {a}");

    // If we don't specify the type we get i32
    let b = -10 + 3;
    print(f"-10 + 3 = {b}");

    let c = 2 * 3;
    print(f"2 * 3 = {c}");

    let d = 20 / 5;
    print(f"20 / 5 = {d}");

    let e = 23 % 5;
    print(f"23 % 5 = {e}");

    // Regular order of operations applies
    let f = 1 + 4 * 5;
    print(f"1 + 4 * 5 = {f}");
}
Output
2 + 1 = 3
-10 + 3 = -7
2 * 3 = 6
20 / 5 = 4
23 % 5 = 3
1 + 4 * 5 = 21

Note that due to Roto’s static typing, we aren’t allowed to perform operations on different types.

Roto
fn main() {
    let a: u8 = 5;
    let b: u32 = 10;

    let c = a + b; // error!
}

Floating point numbers can represent fractional numbers. There are two floating point types: f32 and f64 which are 32- and 64-bit, respectively. They support all the same operators as integers, except for %.

To disambiguate floating point numbers from integers, especially when inferring types, floating point literals must include a period .. So 10 is always an integer, but 10. and 10.0 are always floating point numbers.

Roto
fn main() {
    // We can specify the type to get a specific integer type
    let a: f32 = 2.0 + 1.0; // -> 3.0
    print(f"2.0 + 1.0 = {a}");

    // We always have to write the period for floating point numbers
    let b: f32 = 2. + 1.; // -> 3.0
    print(f"2. + 1. = {b}");

    // If we don't specify the type we get f64
    let c = -12.3 + 4.5; // -> -7.8
    print(f"-12.3 + 4.5 = {c}");

    let d = 2.0 * 3.5; // -> 7.0
    print(f"2.0 * 3.5 = {d}");

    let e = 20.0 / 5.0; // -> 4.0
    print(f"20.0 / 5.0 = {e}");

    // Regular order of operations applies
    let f = 1.0 + 4.0 * 5.0; // -> 21.0
    print(f"1.0 + 4.0 * 5.0 = {f}");
}
Output
2.0 + 1.0 = 3
2. + 1. = 3
-12.3 + 4.5 = -7.800000000000001
2.0 * 3.5 = 7
20.0 / 5.0 = 4
1.0 + 4.0 * 5.0 = 21

See also

Integers in the language reference.

Floating Point Numbers in the language reference.

Arithmetic Operators in the language reference.

u8, u16, u32, u64

i8, i16, i32, i64

f32, f64

Booleans and Comparisons

Booleans in Roto are represented by the bool type. It has two possible values: true and false. Like any built-in type, booleans can be used directly in an f-string to be printed. A boolean can be negated with the ! operator.

Roto
fn main() {
    let a = !false;
    print(f"{a}");
}
Output
true

The other important operators for boolean values are the && and || operators that represent the “logical and” and “logical or” operations, respectively.

Booleans can also be created as the result of comparisons. In Roto, you can use the equality operator == on all types (note that’s a double equals sign; the single equals sign is used for assignment, as we have already seen).

Roto
fn main() {
    let x = 5 == 5;
    let y = "hello" == "hello";
    let z = true == true;
    print(f"{x && y && z}");
}
Output
true

Warning

Take care when using == and != on floats, because they check for exact equality, but floating point numbers are often only approximately equal due to the way they are represented in memory, so two floats that are mathematically equal might not be exactly equal in practice. A nan value (“not a number”) is also not equal to itself.

The opposite of the == is the != operator, which returns true if the operands are not equal.

Integers and floats also support the comparison operators for “less than” (<), “less than or equal” (<=), “greater than” (>) and “greater than or equal” (>=).

Roto
fn main() {
    let x = 5 > 3;
    print(f"{x}");
}
Output
true

See also

Booleans in the language reference

Comparison Operators in the language reference

Logical Operators in the language reference

bool

Lists

Lists are the most common (and currently also the only) collection type in Roto. Create a list by wrapping the set of values in [], and separating the elements using commas. A List’s type is List[T] where T is the type of the elements.

Roto
fn main() {
    let x: List[i32] = [1, 2, 4];
    let len = x.len();
    let contains_2 = x.contains(2);
    let contains_3 = x.contains(3);
    print(f"length of x: {len}");
    print(f"x contains 2: {contains_2}");
    print(f"x contains 3: {contains_3}");
}
Output
length of x: 3
x contains 2: true
x contains 3: false

Unlike in some other scripting languages, all the elements of a list must be of the same type. You’ll get a type checking error if you try to make a list containing an integer and a string for example:

Roto
fn main() {
    let x = [1, "hello!"];
}

You can build larger lists by concatenating multiple lists using the + operator.

Roto
fn main() {
    let x = [1, 2, 3];
    let y = [4, 5, 6];
    let z = x + y;

    let contains_2 = z.contains(2);
    let contains_5 = z.contains(5);
    print(f"z contains 2: {contains_2}");
    print(f"z contains 5: {contains_5}");
}
Output
z contains 2: true
z contains 5: true