What the compiler insists on
The Salam compiler catches a great many mistakes before the program ever runs. Every message says what is wrong, where, and usually how to fix it. This lesson goes through the rules behind the messages you will meet most.
Why so strict?
Every error the compiler catches is an error that never reaches a user. That is why Salam treats as errors several things that other languages only warn about: a variable nobody reads, a mut that never changes, a branch that can never run. Warnings tend to pile up and get ignored; an error is fixed on the spot, so a Salam program that compiles has no such leftovers in it.
Reading an error
A typical message looks like this:
[SEMANTIC][ERROR] E013: cannot reassign immutable variable 'total'; declare it 'mut' to reassign it, or assign to its elements or fields instead (app.salam:3:5)
E013is the error code. It is the same in English and Persian, so it is the thing to search for.- Then comes the problem, and after the semicolon the usual fix. Read the message to the end; the fix is often all you need.
- The end of the line gives the file, line and column: line 3, column 5 of
app.salam.
Mistakes in the shape of the code, such as a missing end or a word that is not Salam, are reported by the parser instead. Those show the line itself with a caret under the spot, and often a help: line, for example error: '&&' is not a Salam operator; write 'and'. A Persian file gets its messages in Persian.
Fix the first error first. One mistake can cause others further down: misspell a name, and the compiler reports both the unknown name (E001) and the variable you meant, which is now unused (E059).
Nothing unused
A variable, parameter, private function or import that nothing uses is an error: E059, E062, E066 and E082. Usually the right fix is to delete it, or to notice that you forgot to use it. Starting the name with _ silences the error, but keep that for the one real case: a signature you do not control. Here every handler must take a user and a text, and Shout has no use for the user:
interface Handler:
func handle(user: str, text: str): str
end
struct Echo:
pub func handle(user: str, text: str): str:
ret user + " said " + text
end
end
struct Shout:
pub func handle(_user: str, text: str): str:
ret text + "!"
end
end
func run(h: dyn Handler):
println h.handle("sara", "hello")
end
func main:
run(Echo {})
run(Shout {})
endWithout the _, the compiler would say E062: unused parameter 'user', and its message itself says to use _ only when the signature is fixed.
mut only where things change
A variable made with := cannot be reassigned (E013). Add mut when it must change, and only then: a mut that never changes is also an error (E069). Adding to a Vector or a HashMap changes the data it points to, not the variable, so push works without mut. A value fixed for the whole program is a const.
const LIMIT := 3
func main:
names := Vector {} as Vector<str>
defer names.free()
names.push("Sara")
names.push("Reza")
names.push("Mina")
names.push("Ali")
mut shown := 0
each n in names:
if shown == LIMIT:
break
end
println n
shown += 1
end
println shown, "of", names.len(), "shown"
endTypes are never guessed
Salam does not convert between types behind your back. Putting a str where an int belongs is E002 in an assignment and E012 in a call, and a condition must be a real bool: if items: with a number is E021, so write if items > 0:. The two branches of ?: must have the same type. Convert explicitly with as, but not when the value already has that type: n as int on an int is a useless cast, E093.
Every path is checked
The compiler follows every path through a function. A function that returns a value must return it on every path (E071: function 'sign' can fall off its end), a match on an enum must cover every member or have an else (E080 names the missing ones), and code after a ret can never run (E070). The same goes for a condition that is always false (E068), an empty if branch (E060) and a statement whose value is thrown away, like n + 1 on its own line (E110).
enum Light: Red, Yellow, Green end
func action(l: Light): str:
ret match l:
Red: "stop" end
Yellow: "slow down" end
Green: "go" end
end
end
func sign(n: int): str:
if n > 0:
ret "positive"
else n < 0:
ret "negative"
end
ret "zero"
end
func main:
println action(Light.Yellow), action(Light.Green)
println sign(-4), sign(0), sign(9)
endWhen a new member is added to Light later, every match that does not handle it stops compiling, which is exactly where you need to look.
No exceptions, and main returns the exit code
Salam has no exceptions and no try and catch. A function that can fail says so in what it returns: a bool with the result in a &: parameter, as below, or a Result enum with the ? operator from the enums lesson. The caller checks it right away.
func parse_age(text: str, age &: int): bool:
n := text.to_int()
if n <= 0 or n > 150:
ret false
end
age = n
ret true
end
func main:
mut age := 0
each text in ["42", "abc", "900"]:
if parse_age(text, age):
println text, "is a valid age"
else:
println text, "is not an age"
end
end
if age == 0:
printerrln "no valid age found"
ret 1
end
println "last valid age:", age
endThe value main returns is the program's exit code: ret 1 reports failure to the shell, and no ret means 0. Calling os.Exit inside main is E109, because it would skip the cleanup that main deferred.
The errors you will meet most
| Code | What you wrote | What the compiler says | The fix |
|---|---|---|---|
E001 | A misspelled name | unknown identifier 'cuont' | Correct the spelling |
E002 | number = "five" | cannot assign 'str' to 'i32' | Use the right type, or convert with as |
E012 | twice("4"), add(1) | argument 1: cannot pass 'str' to 'i32'; 'add' expects 2 arguments, got 1 | Match the function's parameters |
E013 | total := 0 then total = 5 | cannot reassign immutable variable 'total' | Declare it with mut |
E017 | name.shout(), a.balance | str has no method 'shout'; field 'balance' is private in struct 'Account' | Call a method that exists, or make the member pub |
E021 | if items: | if condition must be bool, got 'i32' | Write a comparison: if items > 0: |
E059 | number := 5, never read | unused variable 'number' | Use it or remove it |
E060 | An if with an empty body | the 'if' branch is empty | Fill it, or negate the condition with not |
E062 | A parameter the body never reads | unused parameter 'h' | Remove it, or use _ for a fixed signature |
E066 | A private function nothing calls | unused function 'helper' | Call it or remove it |
E068 | if false: | 'if' condition is always false: its body can never run | Remove the dead branch |
E069 | mut number := 5, never changed | variable 'number' is declared 'mut' but never mutated | Remove mut |
E070 | A statement after ret | unreachable code | Remove it or move it up |
E071 | A path with no ret | function 'sign' can fall off its end without returning a 'str' value | Add a final ret |
E080 | A match that skips a member | non-exhaustive match on enum 'Light': missing member Yellow | Add the member, or an else arm |
E082 | import math, never used | unused import 'math' | Remove the import |
E084 | A const after a function | constant 'RATE' must be declared before any function or type definition | Move it to the top |
E087 | A struct after a function | 'P' must be declared before any function definition | Move the types above the functions |
E090 | A variable with a function's name | 'total' is already the name of a function in this file | Rename one of them |
E093 | n as int on an int | useless cast | Remove the cast |
E108 | An import after an include | 'import str' must come before every include | Put the imports first |
E109 | os.Exit(1) in main | do not call os.Exit in main | Write ret 1 |
E110 | n + 1 alone on a line | this statement has no effect | Assign the result, or remove the line |
A few mistakes are caught by the parser and have no code: while (the loop is until, see the loops lesson), && and || (write and and or), a typed declaration like n: int = 2 (write n := 2), and a reserved word used as a name, such as a parameter called to.
A few hints
- Fix the first message first, then build again; many of the later ones disappear with it.
- Read every message to the end. The part after the semicolon is usually the fix.
- Do not reach for
_orpubto make an error go away. The compiler is pointing at code that is dead, unfinished or in the wrong place. - Search the code, such as
E071, rather than the wording: the code is the same in every language. - Run
salam formatbefore chasing order errors; it moves many definitions into place for you.