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 (").
fn main() {
print("Hello, World!");
}
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.
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 + "!");
}
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.
fn main() {
let first_name = "John";
let last_name = "Doe";
print(f"Hello, {first_name} {last_name}!");
}
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.
fn main() {
let name = "John";
print(f"Hello, {name.to_uppercase()}");
}
Hello, JOHN
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.
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}");
}
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.
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.
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}");
}
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
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.
fn main() {
let a = !false;
print(f"{a}");
}
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).
fn main() {
let x = 5 == 5;
let y = "hello" == "hello";
let z = true == true;
print(f"{x && y && z}");
}
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” (>=).
fn main() {
let x = 5 > 3;
print(f"{x}");
}
true
See also
Booleans in the language reference
Comparison Operators in the language reference
Logical Operators in the language reference
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.
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}");
}
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:
fn main() {
let x = [1, "hello!"];
}
You can build larger lists by concatenating multiple lists using the +
operator.
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}");
}
z contains 2: true
z contains 5: true