Lesson 3 of 25 · Getting started

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

types-basic.salam
func main:
    number := 42
    fraction := 3.14
    text := "hello"
    flag := false
    letter := u'S'
    println number, fraction, text, flag, letter
end
Output42 3.14 hello false S
TypePersianWhat 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.

types-ints.salam
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
end
Output120 9000000000 4000000000 255 10 15

A 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:

types-sizeof.salam
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)
end
Outputi8: 1 int: 4 i64: 8 f64: 8 Point: 8

An 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.

types-overflow.salam
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
end
Outputviews as int: -2147483296 views as i64: 2147484000 stock left: 4294967294

So 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:

types-float.salam
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
end
Output0.30000000000000004 false 10 2.5 1500 0.025 59.97 0.10000000149011612

That 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:

types-char.salam
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
end
OutputA 65 B م 1605 true false

Mixing 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.

types-mix.salam
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
end
Outputaverage price: 19.99 rounded down: 19 one more: 8100000001

Dividing two whole numbers gives a whole number, with the fraction dropped. Convert one side first when you want the fraction:

types-cast.salam
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
end
Output3 1 3.5 3 50

The rules of as in short:

  • Decimal to whole cuts the fraction off toward zero: 3.9 as int is 3 and -7.9 as int is -7. It does not round.
  • A whole number that does not fit the target wraps: 300 as u8 is 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 int is an error. Use to_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.

types-parse.salam
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
end
Output24 5 0 items: 12 total weight: 30

Watch 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.