Types
Salam is statically typed: the type of every value is known before the program runs, and the compiler will not let incompatible types mix.
The basic types
func main:
number := 42
fraction := 3.14
text := "hello"
flag := false
letter := u'S'
println number, fraction, text, flag, letter
end| Type | Persian | What it holds |
|---|---|---|
int | صحیح | A 32-bit whole number |
i8, i16, i64 | صحیح۸, صحیح۱۶, صحیح۶۴ | A whole number of a fixed width |
uint, u8 to u64 | طبیعی, طبیعی۸ to طبیعی۶۴ | An unsigned whole number |
f64, f32 | اعشار۶۴, اعشار۳۲ | A decimal number |
bool | منطقی | true or false |
str | رشته | Text, in Unicode |
char | نویسه | One character, such as u'S' |
usize, size | اندازه مثبت, اندازه | A number the width of a memory address |
Text has a lesson of its own, strings and text. This one is about numbers, booleans and characters, and about moving values from one type to another.
Number sizes and bases
Numbers can be written in base sixteen (0xFF), base two (0b1010) and base eight (0o17), and you can group the digits with _ to make them readable.
func main:
small := 120 as i8
big := 9_000_000_000 as i64
unsigned := 4000000000 as uint
println small, big, unsigned
println 0xFF, 0b1010, 0o17
endA whole number literal is an int, unless it is too big for one: 9000000000 becomes an i64 on its own. sizeof gives the size of a type in bytes:
struct Point:
x: int
y: int
end
func main:
println "i8:", sizeof(i8)
println "int:", sizeof(int)
println "i64:", sizeof(i64)
println "f64:", sizeof(f64)
println "Point:", sizeof(Point)
endAn int runs from about minus two billion to plus two billion, an i64 to about nine quintillion. A u8 holds 0 to 255, which is exactly one byte.
When a number does not fit
If a calculation goes past the end of its type, the number wraps around to the other end and the program carries on without a word. A page counter stored in an int turns negative after about two billion views; the same counter in an i64 keeps going. Unsigned types wrap at zero instead, so taking 5 from 3 gives a huge positive number.
func main:
mut views := 2147483000
views += 1000
println "views as int:", views
mut total := 2147483000 as i64
total += 1000
println "views as i64:", total
stock := 3 as uint
sold := 5 as uint
println "stock left:", stock - sold
endSo pick the type from the largest value you expect, not the first value you see. Use i64 for counters, sizes of files, money in the smallest unit and time in milliseconds, and keep unsigned types for things that really are never negative, such as bytes.
Decimal numbers
A decimal literal such as 2.5 is an f64, and it can be written with an exponent: 1.5e3 is 1500. Printing uses the shortest form that reads back as the same number, so 10.0 prints as 10. Decimal numbers are stored in binary, and most decimal fractions have no exact binary form:
func main:
println 0.1 + 0.2
println 0.1 + 0.2 == 0.3
println 10.0, 2.50, 1.5e3, 2.5e-2
price := 19.99
println price * 3
println 0.1 as f32
endThat tiny error is why you should not compare decimal results with ==; check that the difference is small enough instead. f32 (also called float) saves memory but keeps only about seven digits, as the last line shows. For money, where every cent must add up, use the decimal package, or count in whole cents with an i64.
Booleans and characters
A bool is true or false, and every condition in Salam has to be one. There is no "truthy" number: if count: is an error (if condition must be bool, got 'i32'); write if count != 0:.
A char holds one character. 'A' works for a single byte, such as an English letter or digit; anything else needs the u prefix, as in u'م', and the compiler says so if you forget. Converting a character to int gives its Unicode code, and converting back goes the other way:
func main:
letter := 'A'
println letter, letter as int
next := (letter as int + 1) as char
println next
mim := u'م'
println mim, mim as int
ready := true
println ready, not ready
endMixing types and converting
Arithmetic may widen a value when nothing can be lost: an int with an f64 gives an f64, and an int with an i64 gives an i64. Going the other way is never silent. Storing an f64 in an int variable, or passing an i64 to an int parameter, is an error until you write the conversion with as.
func main:
items := 3
total := 59.97
average := total / items
println "average price:", average
println "rounded down:", average as int
population := 8100000000
println "one more:", population + 1
endDividing two whole numbers gives a whole number, with the fraction dropped. Convert one side first when you want the fraction:
func main:
println 7 / 2
println 7 % 2
println (7 as f64) / 2.0
number := 3.9
println number as int
println "42".to_int() + 8
endThe rules of as in short:
- Decimal to whole cuts the fraction off toward zero:
3.9 as intis 3 and-7.9 as intis -7. It does not round. - A whole number that does not fit the target wraps:
300 as u8is 44. - A cast to the type the value already has is an error (E093 useless cast), so remove it.
- Text never becomes a number through
as:"42" as intis an error. Useto_int(), below.
Text and numbers
Numbers often arrive as text: typed by a user, read from a file, sent by a web form. to_int() and to_float() turn text into numbers, and + with a string on the left turns a number into text.
func main:
qty := "12".to_int()
weight := "2.5".to_float()
bad := "twelve".to_int()
println qty * 2, weight * 2.0, bad
label := "items: " + qty
println label
println "total weight: " + weight * qty
endWatch the third value: text that is not a number gives 0, with no error. If 0 is a value that could really appear, check the text first, for example with the digit tests in the str package. To write a number with a fixed number of decimal places, use conv.FormatFloatPrec from the conv package.
Names for existing types (type Age = int) and brand new types built on them are covered in the new types lesson.