Skip to content

Reference

Primitive types ​

Fourteen primitives, three aliases, one literal suffix. That's every type the compiler knows about on its own, and everything else you write, including string and array<T>, is a library type.

echo
int32 $count = 3;
usize $index = 0;
float64 $ratio = 0.5;
bool $ready = true;

Types is the chapter with the reasoning. This page is the table, the literal grammar, and the exact wording of every conversion the compiler refuses.

The table ​

TypeBitsSignedMinimumMaximum
int88yes-128127
int1616yes-32,76832,767
int3232yes-2,147,483,6482,147,483,647
int6464yes-9,223,372,036,854,775,8089,223,372,036,854,775,807
uint88no0255
uint1616no065,535
uint3232no04,294,967,295
uint6464no018,446,744,073,709,551,615
isizepointer widthyessame as int64 todaysame as int64 today
usizepointer widthno0same as uint64 today
float3232n/aIEEE 754 singleIEEE 754 single
float6464n/aIEEE 754 doubleIEEE 754 double
bool1n/afalsetrue
voidn/an/anot a valuenot a value

bool is one bit in the emitted code and one byte in memory, which is the usual arrangement and never something you have to think about.

Ask for a size yourself and you get the same numbers:

echo
echo mem::size<int32>();     // 4
echo mem::size<bool>();      // 1
echo mem::size<usize>();     // 8

The largest value, by name ​

You will want the top of a count more often than you want to type eighteen digits. Integer primitives answer min() and max() as statics, folded from the same widths the table above uses:

echo
echo usize::max();      // 18446744073709551615
echo int32::min();      // -2147483648
echo uint8::max();      // 255

std::math::MAX_USIZE is the same number as a constant. Reach for the type form when you are already writing usize. There is no float32::max(): floats keep the math constants.

usize and isize are pointer width, and today that is always 8 ​

Their width is one compile-time constant, ECO_TARGET_POINTER_SIZE, and it's 8. There is no per-target logic behind it yet, so on every platform Echo currently builds for, usize is 64 bits.

What matters more than the number is that they are their own types, not aliases. Assigning a uint64 into a usize is a real conversion, not a no-op the compiler waves through:

echo
uint64 $raw = 7;
usize $converted = $raw;
echo $converted;        // 7

That distinction is what keeps every length, count, capacity and index in the standard library spelled usize rather than accidentally spelled uint64 on one platform and uint32 on another.

Three aliases ​

echo
int $a = 1;         // int32
uint $b = 2;        // uint32
float $c = 3.0f;    // float32

There are no others. No double, no byte, no char, no short, no long. int is int32 everywhere and is not the machine word.

numeric, integer, signed, unsigned and floating look like they belong on this list and don't. They are generic constraint aliases, usable only in a type parameter's constraint, never as the type of a variable. See Generics.

What an untyped literal decides ​

LiteralType
25int32, promoted to int64 only if it does not fit
0.5float64
0.5ffloat32
truebool
"hi"string
0xFFuint8, and the width comes from the digit count

The last row is the one that surprises people.

A radix literal picks its width from how you wrote it ​

With no type in front, a 0x or 0b literal counts its digits and picks an unsigned type to fit them. Nothing about the value:

Hex digitsBinary digitsType
1 to 21 to 8uint8
3 to 49 to 16uint16
5 to 817 to 32uint32
9 or more33 or moreuint64

So $e = 0xFF; is a uint8 and $f = 0x00FF; is a uint16, despite being the same number. Write the zeros when you mean the width.

That's only what happens when nothing else has an opinion. A destination outranks it, exactly as it does for a decimal literal, and the range check comes with it:

echo
int32 $x = 0xFF;        // an int32 holding 255, not a uint8
uint64 $b = 0b1011;     // a uint64 holding 11

echo $x;
echo $b;
echo
uint8 $x = 0xFFFF;
// error: The literal '0xFFFF' is too large for the integer type 'uint8'. The maximum value is '255'.

Write hex or binary when you mean a bit pattern, and put the type in front when the width matters.

Every literal form ​

Exists:

FormExampleNotes
decimal integer25, -3the sign is part of the token, see below
decimal float0.5, 1.a trailing dot gets an implicit zero, so 1. is 1.0
float suffix3.14ff is the only suffix in the language, and only on floats
hexadecimal0xFF, 0Xffsee the width rule above
binary0b1011, 0B1011same width rule as hex, counted in bits
string"hi", 'hi'" interpolates {$...}, ' is verbatim
booltrue, false

Does not exist:

  • Octal. No 0o17, no leading-zero form.
  • Digit separators. 1_000 is 1 followed by an identifier _000.
  • Exponent notation. 1e9 stops at the e.
  • Typed integer suffixes. No 25i64, no 25u. Put the type in front instead.
  • Character literals. There is no char type, so there is nothing for 'a' to be. It is a one-byte string.

The minus sign glues to a digit ​

- immediately followed by a digit is read as part of the number, which is what makes -3 a literal rather than a negation of 3. The cost is that a binary - needs spaces around it:

echo
echo 1 - 2;         // -1

Written 1-2 you get unexpected '-2' - two expressions with no operator between them.

Escape sequences ​

EscapeMeans
\n \t \rnewline, tab, carriage return
\0a nul byte
\\ \" \'a literal backslash or quote
\xNNone byte, exactly two hex digits
\u{...}a unicode scalar, one to six hex digits, braces required
echo
echo "tab:\there";      // tab:	here
echo "\x41";            // A
echo "\u{1F600}";       // an emoji

There is no \a, \b, \f or \v, and no unbraced \uXXXX. Anything else after a backslash is an error, not a passthrough. String literals are validated as UTF-8 at compile time.

Conversions ​

There are two separate machines here, and knowing which one you are in is the whole story. A literal is checked at parse time against the destination, because the compiler can see the value. A variable is not checked at all, because it cannot.

A literal is checked ​

CaseResult
fits the destinationconverted at compile time, no runtime cost
integer literal too largeerror
integer literal too smallerror
negative literal into an unsigned typeerror
float64 literal into a float32 that loses bitswarning
float literal with a fraction into an integer typeerror
any literal into a struct or classerror
0 or 1 into a boolfalse or true
any other number literal into a bool, or true/false into a numbererror
hex or binary literalchecked at its destination, like a decimal one

The exact wording, so you can recognise it:

echo
uint8 $no = 256;
// error: Integer overflow: The literal '256' is too large for the integer type 'uint8'.
//        The maximum value is '255'.
echo
uint8 $n = -1;
// error: Invalid type conversion: The integer literal '-1' cannot be implicitly converted to an
//        unsigned integer because it is negative.
echo
float64 $x = true;
// error: Invalid type conversion: a literal of type 'bool' cannot be written where a 'float64' is
//        expected - Echo has no truthiness in a written literal, so say which of the two you meant

true into a number is refused. The other direction is not quite a mirror: bool $ready = 1; is true, bool $ready = 0; is false, and bool $ready = 3; is the error above. 0 and 1 are what echo prints for a bool. 3 is not either of those.

A variable still converts either way, as the next section says. An integer into a bool is compared against zero at runtime. It is only a written literal other than 0 or 1 that has to say which of the two it meant.

The one warning:

echo
float32 $pi = 3.14;
// warning: This operation results in a loss of precision: The literal '3.14' is stored in 32bit float
//          which will result in the effctive value 3.14

Yes, effctive. Writing 3.14f makes the warning go away, because then the literal was a float32 to begin with and no precision was lost on the way in.

A variable is not checked ​

Every conversion in this table happens silently, at runtime, between variables:

FromToWhat happens
narrower intwider intsign-extends if the source is signed, zero-extends if it is unsigned
wider intnarrower inttruncates
intfloatconverted, may lose precision
floatinttruncates toward zero
float64float32rounded
boolint1 or 0
int or floatbooltrue when the value is not zero
echo
int64 $big = 5000000000;
int32 $small = $big;
echo $small;            // 705032704

float64 $pi = 3.14159265358979;
float32 $f = $pi;
echo $f;                // 3.141593

The type checker deliberately does not look at primitive-to-primitive conversions. It refuses conversions between pointers, structs, classes, interfaces, callables, C function pointers and weak handles, and waves numbers through. I am not happy with that. I'd rather narrowing needed something written down, and it is on the list. Until then the literal check is the only safety net you get.

$x as T writes the destination next to the value, for the sites that have none: a return, an operand of <, a field of a constructor about to be called. (int32)$x does not parse and int32($x) is not a function. Casts has the spelling. Narrowing at a typed destination still happens silently. as is the escape hatch, not a requirement.

The other written form is inside an unsafe block, where $bytes:$ as uint8& (or uint8&($bytes:$)) promotes raw storage to a typed borrow. That is a different operation with a similar shape.

void ​

void is the return type that means the function produces nothing. Saying it out loud is required:

echo
function log(string $line) : void
{
    echo $line;
}

log("done");        // done

It is not a local, a field, a parameter, or a type argument. A boundary that can fail and has nothing to hand back returns status<E>.

ptr<void> is refused. Echo's untyped handle is ptr<Handle> over an extern struct. void[4] is refused too: an array needs a size, and void has none.

void is not "the compiler has not worked this out yet". That's a separate internal state you'll never see spelled in your program.

Next ​

  • Types for the reasoning behind these choices, and the value/reference split.
  • Expressions for what happens when you mix two of these in one expression.
  • Keywords for the words that are reserved, none of which are type names.

Echo is a work in progress. Nothing here is a promise of stability.