Skip to main content

Simple types

In Rell, there are several simple data types to represent different kinds of data. They're not composed of other data types and serve as the building blocks for more complex data structures.

Boolean

A simple data type that represents two values: true and false. Booleans are often used for logical operations and decision-making.

val using_rell = true;
function foo() {
if (using_rell) print("Awesome!");
}

Integer

A data type for representing whole numbers without fractional parts. Integers are typically used for counting, indexing, and mathematical operations.

val user_age: integer = 26;
FieldDescription
integer.MIN_VALUEMinimum value (-2^63)
integer.MAX_VALUEMaximum value (2^63-1)
integer(s: text, radix: integer = 10)Parse a signed string representation of an integer, fails if invalid
integer(decimal)Converts a decimal to an integer, rounding towards 0 (5.99 becomes 5, -5.99 becomes -5), throws an exception if the resulting value is out of range
integer.from_text(s: text, radix: integer = 10)Same as integer(text, integer)
integer.from_hex(text)Parse an unsigned HEX representation
.abs()Absolute value
.max(integer)Maximum of two values
.max(decimal)Maximum of two values (converts this integer to decimal)
.min(integer)Minimum of two values
.min(decimal)Minimum of two values (converts this integer to decimal)
.to_text(radix: integer = 10)Convert to a signed string representation
.to_hex()Convert to an unsigned HEX representation
.sign()Returns -1, 0, or 1 depending on the sign
.signum()Returns the sign of the integer: -1 if negative, 0 if zero, and 1 if positive.
.pow(exponent: integer)Raises a number to the power of an exponent.
.hex()Converts this integer to a hexadecimal string.
.parseHex()Parses an unsigned hexadecimal representation of an integer.
.str()Converts this integer to a text string.
.to_big_integer()Converts this integer to a big integer.
.to_decimal()Converts this integer to a decimal.

Big integer

A data type that represents large integers with high precision, capable of handling very large numbers.

val bi: big_integer = 9223372036854775832L;

You must add L to the value to specify that the value must be interpreted and treated as a big_integer.

Internally, the type java.lang.BigInteger gets used in the interpreter, and NUMERIC in SQL.

FieldDescription
big_integer.PRECISIONThe maximum number of digits (131072)
big_integer.MIN_VALUEMinimum value (-(10^131072)+1)
big_integer.MAX_VALUEMaximum value ((10^131072)-1)
big_integer(integer)Creates a big_integer from integer
big_integer(text)Creates a big_integer from a decimal string representation, possibly with a sign. It fails if the string is not valid.
big_integer.to_integer()Converts big_integer to integer. Fails if the value is out of range.
integer.to_big_integer()Converts integer to big_integer
big_integer.to_decimal()Converts big_integer to decimal
decimal.to_big_integer()Converts decimal to big_integer. Truncates the fractional part.
big_integer.to_bytes()Converts a signed (positive or negative) big_integer to a byte_array.
big_integer.from_bytes(byte_array)Converts a byte_array produced by .to_bytes() back to big_integer.
big_integer.to_bytes_unsigned()Converts a positive big_integer to a byte_array. Fails if a negative value is provided.
big_integer.from_bytes_unsigned(byte_array)Converts a byte_array produced by .to_bytes_unsigned() back to big_integer.
.abs()Absolute value of the big_integer
.max(big_integer)Maximum of two values
.max(decimal)Maximum of two values (converts this big_integer to decimal)
.min(big_integer)Minimum of two values
.min(decimal)Minimum of two values (converts this big_integer to decimal)
.to_text()Converts to a decimal string representation.
.from_text(text)Converts from a decimal string representation.
.to_text(radix: integer)Converts to a string representation with a specific base (radix, from 2 to 36)
.from_text(text, radix: integer)Converts from a string representation with a specific base (radix, from 2 to 36)
.to_hex()Convert to an unsigned HEX representation. Supports positive and negative numbers.
.from_hex(text)Parse an unsigned HEX representation. Supports positive and negative numbers.
.sign()Returns -1, 0, or 1 depending on the sign.
.pow()Raises a number to the power of an exponent.

Decimal

A data type for representing real numbers with high precision.

val approx_pi: decimal = 3.14159;
val scientific_value: decimal = 55.77e-5;

It's not a normal floating-point type found in many other languages (like float and double in C/C++/Java):

  • decimal type is accurate when working with numbers within its range. All decimal numbers (results of decimal operations) are implicitly rounded to 20 decimal places. For instance, decimal('1E-20') returns a non-zero, while decimal('1E-21') returns a zero value.

  • Numbers get stored in a decimal form, not binary form, so conversions to and from a string are lossless (except when rounding occurs if there are more than 20 digits after the point).

  • Floating-point types allow storing much smaller numbers, like 1E-300; decimal can only store 1E-20, but not a smaller nonzero number.

  • Operations on decimal numbers may be considerably slower than integer operations (at least ten times slower for the same integer numbers).

  • Large decimal numbers may require a lot of space: ~0.41 bytes per decimal digit (~54KiB for 1E+131071) in memory and ~0.5 bytes per digit in a database.

  • Internally, the type java.lang.BigDecimal gets used in the interpreter, and NUMERIC in SQL.

  • In the code, one can use decimal literals:

    123.456
    0.123
    .456
    33E+10
    55.77e-5

Such numbers have a decimal type. Simple numbers without a decimal point and exponent, like 12345, have integer type.

FieldDescription
decimal.PRECISIONThe maximum number of decimal digits (131072 + 20)
decimal.SCALEThe maximum number of decimal digits after the decimal point (20)
decimal.INT_DIGITSThe maximum number of digits before the decimal point (131072)
decimal.MIN_VALUEThe smallest nonzero absolute value that a decimal can store
decimal.MAX_VALUEThe largest value that you can store in a decimal (1E+131072 - 1)
decimal(integer)Converts an integer to decimal
decimal(big_integer)Converts a big_integer to decimal
decimal(text)Converts a text representation of a number to decimal
.abs()Absolute value
.ceil()Ceiling value: rounds 1.0 to 1.0, 1.00001 to 2.0, -1.99999 to -1.0, etc.
.floor()Floor value: rounds 1.0 to 1.0, 1.9999 to 1.0, -1.0001 to -2.0, etc.
.min(decimal)Minimum of two values
.max(decimal)Maximum of two values
.round(scale: integer = 0)Rounds to a specific number of decimal places to a closer value.
.sign()Returns -1, 0, or 1 depending on the sign
.to_integer()Converts a decimal to an integer, rounding towards 0.
.to_text(scientific: boolean = false)Convert to string.
.from_text()Creates a decimal from a text representation.
.signum()Returns -1, 0, or 1 depending on the sign.
.to_big_integer()Converts this decimal to a big_integer by truncating the fractional part.
note

In Rell, the equality comparison for decimal numbers (using the == and != operators) considers two decimal values equal if they represent the same numerical value, regardless of their specific representation or format.

Example: 1.0E+2 is considered equal to 10.0E+1 , thus, 1.0E+2 == 10.0E+1 returns true.

Text

A data type for representing textual information, such as strings of characters. Text data types allow for text manipulation and processing. Same as string type in some other languages.

val placeholder = "Lorem ipsum donor sit amet";
function foo() {
print(placeholder.size()); // 26
print(placeholder.empty()); // false
}
MethodDescription
text.from_bytes(byte_array, ignore_invalid: boolean = false)If ignore_invalid is false, throws an exception when the byte array is not a valid UTF-8 encoded string, otherwise replaces invalid characters with a placeholder.
.empty()Returns true if the text is empty, otherwise returns false
.size()Returns the number of characters.
.compare_to(text)Returns 0 if texts match, otherwise a positive or negative value
.starts_with(text)Returns true if it starts with the input text, otherwise returns false
.ends_with(text)Returns true if it ends with the input text, otherwise returns false
.contains(text)Return true if contains the given substring, otherwise returns false
.index_of(text)Returns position of input text and -1 if a substring isn't found
.index_of(text, integer)Same as .index_of(text) but starting search from the given position
.last_index_of(text[, start: integer])Returns -1 if a substring isn't found
.sub(start: integer[, end: integer])Get a substring (start-inclusive, end-exclusive)
.repeat(n: integer)Returns the text repeated "n" times
.replace(old: text, new: text)Replaces a substring with new text
.reversed()Returns a reversed copy of the text
.upper_case()Returns a new text with all characters converted to uppercase
.lower_case()Returns a new text with all characters converted to lowercase
.split(text)Strictly split by a separator (not a regular expression)
.trim()Remove leading and trailing whitespace
.matches(text)Returns true if it matches a regular expression
.match_groups(regex: text): list<text>?Matches the entire text against a regular expression and returns a list of captured groups, or null if the match fails. The zeroth element is the entire matched text.
.match_named_groups(regex: text): map<text, text>?Matches the entire text against a regular expression and returns a map of named groups (group names as keys, matched text as values), or null if the match fails. Unnamed groups are not included.
.to_bytes()Convert to a UTF-8 encoded byte array
.char_at(integer)Get a 16-bit code of a character
.format(...)Formats a string. For example, 'My name is <%s>'.format('Bob') - returns 'My name is <Bob>'
.charAt()Gets a 16-bit code of a character.
.compareTo()Compares this text to another text.
.encode()Converts the text to UTF-8 encoded bytes.
.len()Returns the number of characters in the text.
.like()Returns true if this text matches the specified pattern using SQL LIKE syntax.

Regular expression match groups

The match_groups() and match_named_groups() methods retrieve capturing groups from a regular expression match. Both functions match the entire text value against the specified regular expression (as opposed to searching for a match), returning null if the match fails.

match_groups(regex: text): list<text>?

Returns a list<text> containing the text matched to each match group, the zeroth of which is the entire matched text. Returns null if the match fails.

'johnsmith@chromaway.com'.match_groups('([a-z]+)@([a-z]+[.][a-z]+)')
// Returns: ['johnsmith@chromaway.com', 'johnsmith', 'chromaway.com']

'XYZ'.match_groups('(X(Y))(Z)')
// Returns: ['XYZ', 'XY', 'Y', 'Z']

'XYZ'.match_groups('((X(Y))(Z))')
// Returns: ['XYZ', 'XYZ', 'XY', 'Y', 'Z']

'X'.match_groups('(X)|(Y)')
// Returns: ['X', 'X', ''] (the third group (Y) matches nothing, hence the empty string)

'XYZ'.match_groups('(?<x>X(?<y>Y))(?<z>Z)')
// Returns: ['XYZ', 'XY', 'Y', 'Z']

match_named_groups(regex: text): map<text, text>?

Returns a map<text, text> containing match group names as keys and their matching text as values. Returns null if the match fails. Text matched to unnamed groups is not returned (use match_groups() instead for these).

'XYZ'.match_named_groups('(?<x>X(?<y>Y))(?<z>Z)')
// Returns: ['x': 'XY', 'y': 'Y', 'z': 'Z']

'XYZ'.match_named_groups('(?<x>X(Y))(Z)')
// Returns: ['x': 'XY'] (only named groups are included)

'johnsmith@chromaway.com'.match_named_groups('(?<user>[a-z]+)@(?<domain>[a-z]+[.][a-z]+)')
// Returns: ['user': 'johnsmith', 'domain': 'chromaway.com']

'XYZ'.match_named_groups('(X(Y))(Z)')
// Returns: map<text, text>() (no named groups in the regex)

'X'.match_named_groups('(?<x>X)|(?<y>Y)')
// Returns: ['x': 'X'] (only matched alternatives are included)

'X'.match_named_groups('(?<x>X)(?<y>Y?)')
// Returns: ['x': 'X', 'y': ''] (named groups matching empty string with '?' quantifier are included)

Match groups and named groups

Match groups in a regular expression are defined by any parentheses that the regular expression contains, and the groups are ordered by the position of their opening parentheses. For example, the regular expression (X(Y))(Z) contains 3 matching groups:

  1. (X(Y)) - group 1
  2. (Y) - group 2
  3. (Z) - group 3

Named groups are match groups in a regular expression for which a name is specified. The match groups in the above example (X(Y))(Z) could be assigned names in the following manner:

(?<x>X(?<y>Y))(?<z>Z)

This is an equivalent regular expression, but the text matched by each group can be referenced by the group's name.

Both functions support non-capturing groups (notated (?:X), where X is a regular expression), but do not include such groups in the returned list or map.

Here are the options available for the text.format() function in Rell:

Format specifierData typeOutput description
%bbooleanFormats boolean values as "true" or "false".
%BbooleanFormats boolean values as "TRUE" or "FALSE".
%stextRepresents a placeholder for text arguments.
%fdecimalFormats a decimal to 6 decimal places.
%dintegerFormats an integer as a base-10 number.
%ointegerFormats an integer as an octal number.
%xintegerFormats an integer as a hexadecimal number (with lowercase alphabetic characters).
%XintegerFormats an integer as a hexadecimal number (with uppercase alphabetic characters).

You can use most of these text functions within at-expressions where they're translated to their SQL equivalents.

Special operators:

  • +: concatenation
  • []: character access (returns single-character text)

Byte array

Byte arrays are used to handle binary data, such as hexadecimal or Base64 representations. You can create byte arrays from text or lists of integers, convert them to different representations, and perform operations like concatenation and element access.

val user_pubkey: byte_array = x"0373599a61cc6b3bc02a78c34313e1737ae9cfd56b9bb24360b437d469efdf3b15";
function foo() {
print(user_pubkey.to_base64()); /A3NZmmHMazvAKnjDQxPhc3rpz9Vrm7JDYLQ31Gnv3zsV
}
FieldDescription
byte_array(text)Creates a byte_array from a HEX string, for example, '1234abcd'
byte_array.from_hex(text): byte_arraySame as byte_array(text)
byte_array.from_base64(text): byte_arrayCreates a byte_array from a Base64 string
byte_array.from_list(list<integer>): byte_arrayConverts list to a byte array; values must be 0 - 255
.empty()Returns true if the byte_array is empty, otherwise returns false
.repeat(n: integer): byte_arrayReturns the byte array repeated "n" times
.reversed()Returns a reversed copy of the byte array
.size()Returns the number of bytes in the byte array
.sub(start: integer[, end: integer]): byte_arraySub-array (start-inclusive, end-exclusive)
.to_hex()Returns a HEX representation of the byte array, for example, '1234abcd'
.to_base64()Returns a Base64 representation of the byte array
.to_list()Returns a list of values 0 - 255
.sha256()Returns the SHA256 digest as a byte_array
.len()Returns the number of bytes.
.join_to_text()Creates a text from all the elements separated using separator and using the given prefix and postfix if supplied. If the iterable is large, you can specify a non-negative value of limit, in which case only the first limit of elements will be appended, followed by the truncated text (which defaults to "...").

You can use most of these byte array functions within at-expressions, where they're translated to their SQL equivalents.

Special operators:

  • +: concatenation
  • []: element access
💡 Best practice
▶️ Validate length of variables early

When working with text or byte_array, validate the length at the start of your operation. This ensures data integrity and improves performance.

val MAX_TEXT_LENGTH = 1000;

operation op(x: text) {
require(x.size() <= MAX_TEXT_LENGTH);
}

ROWID

The primary key of a database record, a 64-bit integer, supports only comparison operations.

When an entity row is inserted into the database using the create expression, it gets a new unique rowid value from the same sequence which is greater than the last allocated rowid in the same blockchain. Deleting an entity row doesn't affect rowids generation. For example, let's assume we have 2 entities a and b:

create a ()//rowid = N0
create b ()//rowid N1, N1 > N0
create a ()//rowid N2, N2 > N1
delete a { .rowid == N2 }
create b ()//rowid N3, N3 > N2

rowid supports only comparison operations.

FieldDescription
rowid(integer): rowidConverts an integer to rowid. The value must not be negative.
rowid.to_integer(): integerReturns the integer value of the rowid.

JSON

The json type represents arbitrary JSON values. It is stored in PostgreSQL as the JSON type and is typically created from text.

val json_text = '{ "name": "Alice" }';
val json_value: json = json(json_text);
function foo() {
print(json_value);
}
FieldDescription
json(text)Create a json value from a string; fails if not a valid JSON string.
.to_text()Convert the json value to a string.
.str()Convert the json value to a string.

Value retrieval

You can retrieve elements from JSON objects and arrays using methods or the [] index operator.

Strict getters (throw on type mismatch or missing index/key):

json.get(index: integer): json
json.get(key: text): json
  • json.get(index: integer) – get the element at the specified index in a JSON array. Throws an exception if the value is not an array or the index is out of bounds.
  • json.get(key: text) – get the element with the specified key in a JSON object. Throws an exception if the value is not an object or the key does not exist.

Nullable getters (return null instead of throwing):

json.get_or_null(index: integer): json?
json.get_or_null(key: text): json?

These behave like get(...), but return null when the target is not an array/object or the index/key does not exist.

Conversion functions

Convert JSON primitive values to Rell values.

Strict conversions (throw errpr):

json.as_integer(): integer
json.as_big_integer(): big_integer
json.as_text(): text
json.as_boolean(): boolean

These functions throw an exception if the JSON value is not of the expected type; for example, json("42").as_text() is valid, but calling .as_text() on a JSON boolean will fail.

All JSON values that can be converted with as_integer() can also be converted with as_big_integer(). The reverse is not always true, because big_integer has a larger range than integer.

Nullable conversions (return null instead of throwing):

json.as_integer_or_null(): integer?
json.as_big_integer_or_null(): big_integer?
json.as_text_or_null(): text?
json.as_boolean_or_null(): boolean?

These mirror the strict conversion functions but return null when the JSON value is not of the expected type instead of throwing an exception.

Type checking

Use the following helpers to inspect the type of a JSON value:

json.is_object(): boolean
json.is_array(): boolean
json.is_text(): boolean
json.is_integer(): boolean
json.is_big_integer(): boolean
json.is_boolean(): boolean
json.is_null(): boolean

Each function returns true if the JSON value is of the type in the method's name, or false otherwise. All JSON values that return true for is_integer() also return true for is_big_integer(), but not vice versa, since big_integer has a larger range.

Utility functions

json.size(): integer
json.keys(): set<text>
  • size() – for arrays, returns the number of elements; for objects, returns the number of key–value pairs. Throws an exception if the value is not an array or an object.
  • keys() – returns the set of keys of a JSON object. Throws an exception if the value is not an object.

Index operator []

The json type supports an index operator [], which is equivalent to the corresponding get() method:

  • value[index] is equivalent to value.get(index).
  • value[key] is equivalent to value.get(key).

Examples:

json('{"x": 1, "y": 2}')['x']      // returns json('1')
json('["a", "b", "c"]')[1] // returns json("b")

You can chain [] and the conversion helpers to navigate JSON trees:

val user_data: json = json('{
"user": {
"id": 123,
"profile": {
"nickname": null,
"preferences": { "theme": "dark", "volume": 5 }
},
"age": 30
},
"sessions": [
{ "id": "s1", "meta": { "started": "2025-10-10T09:00:00Z", "ip": "10.0.0.1" } },
{ "id": "s2", "meta": { "started": "2025-10-09T18:12:00Z" } }
]
}');

user_data["sessions"][0]["id"].as_text(); // "s1"
user_data["user"]["profile"]["preferences"]["volume"].as_integer(); // 5

Unit

No value; can't use it explicitly. Equivalent to [unit]{.title-ref} type in the Kotlin programming language.

Null

Type of null expression; can't use it explicitly.

Simple type aliases

  • pubkey = byte_array
  • name = text
  • timestamp = integer
  • tuid = text