Threadon / Types

Types

every value has one, and it's checked

The type list

Threadon has sixteen built-in types.

TypeWhat it holdsNotes
Int88-bit signed integerwraps, range -128 to 127
Int1616-bit signed integerwraps, range -32,768 to 32,767
Int3232-bit signed integerthe default integer, wraps, range -2,147,483,648 to 2,147,483,647
Int6464-bit signed integerwraps, range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
UInt88-bit unsigned integerwraps, range 0 to 255
UInt1616-bit unsigned integerwraps, range from 0 to 65,535
UInt3232-bit unsigned integerwraps, range from 0 to 4,294,967,295
UInt6464-bit unsigned integerwraps, range from 0 to 18,446,744,073,709,551,615
Float1616-bit floatprints like a normal float, half precision
Float3232-bit floatthe default float, single precision
Float6464-bit floatIf you want more precision and a bigger range, double precision
Boolboolean valuetrue or false (prints contextually based on language)
Stringtextsequence of characters, join with +
NoneTypeno valueonly used for return types or representing absence of value
List[T]an ordered collection of items all of one typee.g. [1, 2, 3]; index with xs[0], slice with xs[1:3]; loop over it; grow it with append/pop/insert/remove
Dict[K, V]an unordered map from one type to anothere.g. {"a": 1}; look up with d["a"]; loop over the keys

Besides these, you can define your own struct and class types. See Structs and the standard library (which defines an Iterable class for range).

The two collection types are covered in more detail on the Flow page, which shows looping over them, growing and shrinking lists, and the Builtins page, which shows their length. Text is covered on the Strings page.

Integers

A plain number in your code, like 42, is an Int32. Use the cast syntax to store it in a smaller type:

a: Int8 = Int8(10)
b: Int16 = Int16(300)
c: Int32 = 70000

If you go past the range of an integer you'll get an error if you've enabled --debug and if you're using floats you will get an error if you use the flag --flag-inf.

Look at what happens as an Int8 climbs past its maximum of 127:

def main() -> Int32
    print(Int8(127))
    print(Int8(128))
    print(Int8(255))
    return 0
RuntimeError: compiler error: Error: Integer literal 128 out of range for type 'Int8' at line 3 print(Int8(128)) ^

It won't even compile. But if you let the user input a number you've still that risk

def main() -> Int32
    print(Int8(input("input a number")))
    return 0
input a number: 128 [RUNTIME ERROR] │ Error: Integer overflow in string conversion (value out of range for Int8) ├─> Location: String → Int8 cast └─> Process terminated with exit code 1

Floats

A number with a decimal point, like 2.5, is a Float32. Float16 is the smaller sibling and Float64 the bigger sibling.

Floats print with six decimals, always. There is no formatting control yet.

def main() -> Int32
    print(Float32(2.5))
    print(Float64(0.1))
    print(Float16(1.5))
    return 0
2.500000 0.100000 1.500000

Note that print(2.5) and print(Float32(2.5)) print the same thing. The literal is already a float.

Bools

Bool holds true or false. The literals are True and False. Conditions must be bools, so comparisons return bools.

def main() -> Int32
    print(True)
    print(False)
    print(3 < 5)
    print(3 == 5)
    return 0
True False True False

Strings

A String is a piece of text, written with double quotes.

name: String = "John"

You can join strings with +, and turn numbers and bools into strings with String(...):

greeting: String = "Hello " + name
print(greeting)
print(String(42))
Hello John 42

Strings support indexing (s[0]), slicing (s[1:4]), and comparison by content (s[1:4] == "ell" is True), plus eager f"..." and lazy t"..." interpolated strings. Those all live on the dedicated Strings page.

What you can't do with a string: there's no len, no iterating with for, no editing of individual characters, and no searching or splitting. Rough edges are collected on the Limits page.

NoneType

NoneType means "no value". It is used as the return type of functions that don't return anything.

Always have a return in a function. You can type just returnWithout a value but the program needs to return something on every path

This


  def main() -> Int32:
      if 1 == 1:
          return 0
      else:
          print()
  

is illegal.

Error: Function must return a value on all code paths at line 5 print() ^
def say_hi() -> NoneType
    print("hi")
    return

Values that were never set

If you declare a variable without giving it a value, it starts at zero. Integers start at 0, floats at 0.0, bools at false.

def main() -> Int32
    n: Int32
    f: Float32
    b: Bool
    print(n)
    print(f)
    print(b)
    return 0
0 0.000000 False

It's still a good idea to set a value yourself. The zero behavior is a convenience, not a promise, and relying on it makes code harder to read.

The same applies to reading a key that isn't in a Dict: {"a": 1}["b"] gives you the zero value of the value type, silently. With --debug the lookup fails with a KeyError instead. See Limits and Debug.

Struct and class fields follow the same rule: a field you don't set — left out of an initializer or never assigned in __init__ — holds the zero value of its type. See Structs and Classes.

Strict type checking

Threadon checks types at compile time and is strict about it. Two things you will hit immediately:

The one lenient case: an integer literal can adapt to the type of the variable you assign it to. x: Int8 = 5 works, even though 5 on its own is an Int32. The literal is retyped to fit. See Variables.

Everything else has to match exactly, or you convert it with a cast.