The type list
Threadon has sixteen built-in types.
| Type | What it holds | Notes |
|---|---|---|
Int8 | 8-bit signed integer | wraps, range -128 to 127 |
Int16 | 16-bit signed integer | wraps, range -32,768 to 32,767 |
Int32 | 32-bit signed integer | the default integer, wraps, range -2,147,483,648 to 2,147,483,647 |
Int64 | 64-bit signed integer | wraps, range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
UInt8 | 8-bit unsigned integer | wraps, range 0 to 255 |
UInt16 | 16-bit unsigned integer | wraps, range from 0 to 65,535 |
UInt32 | 32-bit unsigned integer | wraps, range from 0 to 4,294,967,295 |
UInt64 | 64-bit unsigned integer | wraps, range from 0 to 18,446,744,073,709,551,615 |
Float16 | 16-bit float | prints like a normal float, half precision |
Float32 | 32-bit float | the default float, single precision |
Float64 | 64-bit float | If you want more precision and a bigger range, double precision |
Bool | boolean value | true or false (prints contextually based on language) |
String | text | sequence of characters, join with + |
NoneType | no value | only used for return types or representing absence of value |
List[T] | an ordered collection of items all of one type | e.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 another | e.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
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
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
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
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))
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.
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
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:
- You can't mix integer and float in the same expression.
1 + 2.5is a compile error. Convert first:Float32(1) + 2.5. - You can't compare different types.
1 == 1.0is a compile error. - You can't assign a value to a variable of a different type.
x: Int32 = 3.5is a compile error.
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.