Data Types
Every piece of data in saQut has a type. The type determines what kind of value a variable can hold and what operations you can perform on it.
Primitive Types (Value Types)
Section titled “Primitive Types (Value Types)”These types hold their value directly. When you assign one to another, the value is copied, so the two variables become independent.
int (Integer)
Section titled “int (Integer)”A 32-bit signed whole number.
int a = 42;int b = -100;int c = 0xFF; // hexadecimal, 255int d = 0b1010; // binary, 10int e = 0777; // octal, 511Range: -2,147,483,648 to 2,147,483,647. Overflow wraps with defined
two’s-complement behavior (it does not throw). If you need a wider integer,
use longint.
longint (64-bit Integer)
Section titled “longint (64-bit Integer)”A 64-bit signed whole number, for values that do not fit in int (file
sizes, offsets, hashes, timestamps).
longint big = 9223372036854775807; // int64 maxlongint scaled = big + 1; // wraps to -9223372036854775808int small = 42;longint widened = small; // int to longint is lossless, allowedRange: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
longint stays out of the numeric conversion tower on purpose. Widening an
int to longint is lossless and implicit. The reverse (longint to int)
and mixing longint with float, double, or decimal require an explicit
as cast, because those conversions can lose data and saQut does not perform
them silently.
float (32-bit Floating-Point)
Section titled “float (32-bit Floating-Point)”A 32-bit IEEE 754 single-precision number. It carries about 7 significant decimal digits, so results show single-precision rounding.
float x = 3.14;float y = 0.5;float f = 0.1;print(f + 0.2); // 0.300000012 (single precision)double (64-bit Floating-Point)
Section titled “double (64-bit Floating-Point)”A 64-bit IEEE 754 double-precision number, about 15 to 16 significant digits.
A bare decimal literal like 0.2 is a double by default; it becomes a
float only in a float context. Use scientific notation with e or E.
double d = 0.1;print(d + 0.2); // 0.3double z = 1e5; // 100000.0double w = 2.5e-3; // 0.0025float and double are stored at their real widths, so a double to float
cast loses precision. That conversion needs an explicit as float.
bool (Boolean)
Section titled “bool (Boolean)”A logical value, either true or false. Stored internally as an int
(1 for true, 0 for false).
bool isReady = true;bool done = false;byte (Unsigned 8-bit)
Section titled “byte (Unsigned 8-bit)”A small unsigned integer between 0 and 255.
byte b = 100;byte c = 200;// byte d = 300; ERROR, out of rangebyte automatically promotes to int in arithmetic (e.g. byte + byte → int).
Use as byte to cast back.
byte a = 200;byte b = 100;int sum = a + b; // byte → int promotionbyte result = sum as byte; // explicit cast backType Promotion Rules
Section titled “Type Promotion Rules”saQut does not perform implicit (automatic) conversions between different
types without an explicit as cast. This avoids silent data loss.
Arithmetic Widening
Section titled “Arithmetic Widening”When two numeric types are used in an arithmetic operation (+, -, *,
/, %, etc.), the result type is the wider of the two operands:
int a = 5;float b = 3.0;
// int + float → floatfloat result = a + b; // 8.0 (int promoted to float)The numeric ranking from narrowest to widest:
| Rank | Type |
|---|---|
| 0 | int |
| 1 | float |
| 2 | double |
| 3 | decimal |
So:
int + int→intint + float→floatfloat + double→doublebyte + byte→int(byte is promoted to int first)
longint is not in this table. It has its own rule: longint + int promotes
the int and gives longint, but longint never mixes with float,
double, or decimal without an explicit cast.
Worked examples
Section titled “Worked examples”int a = 5;float b = 2.0;
int x = a + a; // int + int → int → 10float y = a + b; // int + float → float → 7.0float z = b + b; // float+float → float → 4.0The rule looks at the operands, not at the variable you assign into. This leads to one gotcha worth remembering:
float half = 1 / 2; // → 0.0, NOT 0.5 !Why? Both 1 and 2 are integers, so 1 / 2 is computed as an integer
division first, which truncates toward zero and gives 0. Only then is
that 0 widened to 0.0 for the float variable. Declaring the target as
float does not change how the division itself is done.
To get 0.5, make at least one operand a float so the whole operation happens
in float:
float ok = 1.0 / 2; // → 0.5 (one float operand promotes the other)Integer division always truncates (drops the fractional part):
print(10 / 3); // 3 (not 3.333)print(1 / 2); // 0print(7 % 3); // 1 (modulo, the remainder)No Implicit Narrowing
Section titled “No Implicit Narrowing”You cannot assign a wider type to a narrower type without an explicit cast:
int a = 1.5; // ERROR, float literal to int (E003)float b = 42; // OK, int literal in float contextint c = b; // ERROR, float to int requires 'as'int d = b as int; // OK, explicit cast (truncates)Literal Context-Typing
Section titled “Literal Context-Typing”Integer literals adapt to the expected context:
float x = 1; // OK, literal 1 becomes 1.0 in float contextBut float literals can never silently become integers:
int y = 1.5; // ERROR, float literal in int context (E003)Compound Types (Reference Types)
Section titled “Compound Types (Reference Types)”These types hold a reference to the data. When you assign one to another, both variables share the same underlying value. Changing one affects the other.
string (Text)
Section titled “string (Text)”An immutable sequence of UTF-8 characters.
string s = "Hello, saQut!";string empty = "";string is a value semantically (copy-on-write), but at runtime it is a
reference type. Learn more about strings.
struct (Group of Fields)
Section titled “struct (Group of Fields)”A custom composite type that bundles multiple values under one name.
struct Point { int x; int y;}
Point p;p.x = 10;p.y = 20;Type[] (Array)
Section titled “Type[] (Array)”A fixed-length sequence of values of the same type.
int[] numbers = [1, 2, 3, 4, 5];string[] names = ["ali", "veli", "deli"];Nullable Types
Section titled “Nullable Types”Any type can be made nullable by adding ? after it. A nullable variable
can hold null in addition to its normal values.
int? maybeNumber = 42;maybeNumber = null; // OK, it's nullable
int normalNumber = 42;// normalNumber = null; ERROR, int cannot hold nullNullable types are checked at compile time. You cannot use a nullable value
without first checking it is not null:
int? x = getValue();
// print(x); ERROR, x might be null
if (x != null) { print(x); // OK, narrowed to non-null}Use the as operator to safely convert. If the conversion fails, it returns
null when the target is nullable:
string s = "42";int? n = s as int?; // 42if (n != null) { /* use n */ }Enum Types
Section titled “Enum Types”An enum defines a set of named constants.
enum Color { Red, Green, Blue }
void main() { Color c = Color.Green; print(c); // outputs 1 (the index)}Enums work with switch:
switch (c) { case Color.Red: print(0); case Color.Green: print(1); case Color.Blue: print(2);}Summary Table
Section titled “Summary Table”| Type | Category | Assignment | Default |
|---|---|---|---|
int |
Primitive | Copy | 0 |
float |
Primitive | Copy | 0.0 |
bool |
Primitive | Copy | false |
byte |
Primitive | Copy | 0 |
string |
Compound | Reference | "" |
struct |
Compound | Reference | Zero fields |
Type[] |
Compound | Reference | null |
Type? |
Nullable | Reference | null |
