Variables and types

If you come from Python, JavaScript or Java, you are used to a number just being a number. In Vesta you choose how many bits it takes, and that choice has consequences.

site/snippets/types.vx
i32 main() {
    // Enteros con signo: el numero dice cuantos bits ocupa.
    i8  pequeno = 100;
    i32 normal  = 70000;
    i64 grande  = 9000000000;

    // Sin signo: mismo tamano, pero solo valores positivos.
    u8  byte  = 255;
    u64 ancho = 18000000000000000000;

    // Coma flotante.
    f32 simple = 2.5;
    f64 doble  = 3.14159;

    // Booleano y caracter.
    bool activo = true;
    char letra  = 'A';

    // Cadena.
    string saludo = "hola";

    println("i8=${pequeno} i32=${normal} i64=${grande}");
    println("u8=${byte} u64=${ancho}");
    println("f32=${simple} f64=${doble}");
    println("bool=${activo} char=${letra:char} string=${saludo}");

    // El mismo numero, escrito en cuatro bases.
    i64 dec = 42;
    i64 hex = 0x2A;
    i64 bin = 0b101010;
    i64 oct = 0o52;
    println("42 = ${dec} = ${hex} = ${bin} = ${oct}");

    // El tipo se puede deducir del valor inicial.
    auto contador = 0;
    auto ratio = 1.5;
    println("auto: ${contador} y ${ratio}");

    return 0;
}
i8=100 i32=70000 i64=9000000000
u8=255 u64=18000000000000000000
f32=2.5 f64=3.14159
bool=true char=A string=hola
42 = 42 = 42 = 42 = 42
auto: 0 y 1.5

Why the type states its size

i64 means signed 64-bit integer. The number is not decoration: it says exactly how much memory the variable takes and, therefore, which values fit in it.

That matters for two reasons. The first is capacity: an i8 holds 256 distinct values and not one more, so if you store 300 the result will not be 300. The second is space: a million i64 values take eight megabytes and a million i8 values take one. When you handle large structures, or talk to hardware that expects an exact layout, that difference is the whole point of the language.

Languages that hide the size pick for you, usually the largest one, and in exchange spare you the thought. Vesta lets you choose because sometimes the choice is the problem you are solving.

The primitive types

Signed integers:

TypeBitsFromTo
i88-128127
i1616-32,76832,767
i3232-2,147,483,6482,147,483,647
i6464-9,223,372,036,854,775,8089,223,372,036,854,775,807

Unsigned integers:

TypeBitsFromTo
u880255
u1616065,535
u323204,294,967,295
u6464018,446,744,073,709,551,615

Signed versus unsigned changes which values fit, not how many. A u8 and an i8 both take one byte and both hold 256 distinct values; what changes is where the range starts and ends.

And the rest:

TypeBitsWhat it is
f3232Single-precision floating point
f6464Double-precision floating point
bool8true or false
char32A Unicode code point
string-Text, managed by the runtime

Four ways to write the same number

i64 dec = 42;
i64 hex = 0x2A;
i64 bin = 0b101010;
i64 oct = 0o52;

Those are the same value written in base ten, sixteen, two and eight. Which one you pick depends on what you are expressing: a bit mask reads far better in binary than in decimal.

Letting the type be inferred

When the initial value already says what the type is, auto saves repeating it:

auto contador = 0;      // i64
auto ratio = 1.5;       // f64

It works for local variables. It is sugar, not magic: the type is pinned down at build time exactly as if you had written it.

auto currently fails with strings. auto s = "hola"; does not infer string, and printing it shows a number instead of the text. Write string s = "hola"; until this is fixed.

char is a number

A char holds a Unicode code point, and interpolating it shows that number:

char letra = 'A';
println("${letra}");         // 65
println("${letra:char}");    // A

${expr:char} is a format specifier: it says how you want the value written, not what the value is. There are more, such as :hex for hexadecimal, and they get their own chapter in the strings part.

Converting between types

Going to a wider type loses nothing and needs no asking:

i32 a = 7;
i64 b = a;      // plenty of room

The other way round does lose information, and here you want to be careful:

i64 grande = 300;
i8 pequeno = (i8) grande;   // 44

300 does not fit in an i8, so the low bits are kept and the result is 44. The (i8) is a cast: you are telling the compiler you know what you are doing.

The cast is not required, and that is the trap. i8 pequeno = grande; without a cast also compiles today, also gives 44, and warns about nothing. Write the cast whenever you narrow: it does not change the result, but it records that you meant it.

Constants

const marks a value that cannot be reassigned:

const i64 MAXIMO = 100;

Trying to change it is a compile error. Remember the limitation from the previous chapter: at top level, f32 and f64 constants do not yet resolve their name when used.

What comes next

You have values now. Next comes operating on them: arithmetic, comparisons, bit logic and the precedence rules.