Compile error
Some mistakes are better caught by the compiler than by whoever runs the program. A program that needs 64-bit pointers should refuse to be built for anything else, with a message that says why, instead of building and then misbehaving. A function that has a better replacement should tell everyone who still calls it. These are messages from the source code to the person building it, and three directives carry them:
| Directive | Effect |
|---|---|
#Error("…") | stops the build with that message |
#Warn("…") | prints a warning and lets the build carry on |
#Allow("rule") | silences one rux lint rule for the declaration below it |
Stopping the build with #Error
#Error stops the build wherever the compiler reaches it. So it is almost always written inside a when branch — the branch a build should never take:
when #target.pointerBits < 64 {
#Error("this program needs a 64-bit target");
}
Every target Rux supports today has 64-bit pointers, so this branch is never taken, and a branch that is not taken is never looked at: the directive in it never fires. Flip the condition to >= 64 and the build stops on the spot, pointing at the directive:
Src/Main.rux:35:9: error: this program needs a 64-bit target
Used like this, #Error is a statement, so it is imported from Core like any other name: import Core::{ #Error, #target };. Its message must be a string literal written right there — the compiler prints it while compiling, long before any variable has a value.
Warning every caller with #Warn
Written above a declaration, as an attribute, a directive fires at every use of that declaration rather than where it is written. That is how an old function points its callers at the new one:
// Fires at each call below, as a warning; the program still builds and runs.
#Warn("Average rounds toward zero; call AverageRounded instead")
func Average(total: int, count: int) -> int {
return total / count;
}
The warning at the top of the output below comes from this attribute. It points at line 38, column 41 — the call to Average in Main, not the declaration — and the program still builds and runs. #Error works as an attribute too: the build then stops at the first call, which is how a removed function can explain where to go instead of just vanishing.
As an attribute, neither directive needs an import; that is why the program imports #Error but not #Warn. Written as a statement inside a body, #Warn("…"); is imported from Core exactly like #Error.
| Written as | Fires | Import from Core |
|---|---|---|
a statement, #Error("…"); | where it is reached; stops the build | yes |
a statement, #Warn("…"); | where it is reached; build carries on | yes |
| an attribute on a declaration | at every use of that declaration | no |
Silencing one lint rule with #Allow
rux lint checks style rules the compiler does not. One of them asks for PascalCase constant names, which would turn kilobytes (kB) into KB. The spelling is deliberate here, so the rule is switched off for this one declaration and nowhere else:
#Allow("naming.const")
const kB: int = 1000;
Without the attribute, rux lint reports warning: constant name 'kB' should be PascalCase and suggests renaming it to KB. The rules #Allow accepts are naming.type, naming.const and docs.missing.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Some mistakes are better caught by the compiler than by whoever runs the program. Three
// directives let source code speak to the build:
//
// - `#Error("...")` stops the build with that message, wherever it is reached.
// - `#Warn("...")` prints a warning and lets the build carry on.
// - `#Allow("rule")` silences one `rux lint` rule for the declaration it is attached to.
//
// `#Error` is almost always written inside a `when` branch: the branch a build should never take.
// A branch that is not taken is never looked at, so the directive in it never fires. Written as
// an attribute on a declaration instead, `#Error` or `#Warn` fires at every use of that
// declaration, which is how an old function points its callers at the new one.
import Core::{ #Error, #target };
import Io::PrintLine;
// Fires at each call below, as a warning; the program still builds and runs.
#Warn("Average rounds toward zero; call AverageRounded instead")
func Average(total: int, count: int) -> int {
return total / count;
}
func AverageRounded(total: int, count: int) -> int {
return (total + count / 2) / count;
}
// `rux lint` asks for PascalCase constant names, which would turn kilobytes (kB) into KB. The
// spelling is deliberate here, so the rule is switched off for this one declaration.
#Allow("naming.const")
const kB: int = 1000;
func Main() -> int {
// Every supported target has 64-bit pointers, so this branch is never taken. Flip the
// condition to `>= 64` and the build stops with:
// Src/Main.rux:35:9: error: this program needs a 64-bit target
when #target.pointerBits < 64 {
#Error("this program needs a 64-bit target");
}
PrintLine("Average of 7 and 8: {}", Average(15, 2));
PrintLine("Rounded average: {}", AverageRounded(15, 2));
PrintLine("A 3 kB file holds {} bytes", 3 * kB);
return 0;
}
Besides Io, its Rux.toml lists Core under [Dependencies].
Run it
cd Examples/CompileTime/CompileError
rux run
Src\Main.rux:38:41: warning: Average rounds toward zero; call AverageRounded instead
38 | PrintLine("Average of 7 and 8: {}", Average(15, 2));
| ^
note: compiler phase: Analyzing
Average of 7 and 8: 7
Rounded average: 8
A 3 kB file holds 3000 bytes
The compiler prints the file's full path where this shows Src\Main.rux.
Common mistakes
#Error with no when around it.A directive that is always reached always fires. Written straight into a body,
#Error("…"); stops every build, on every target. Put it in the branch that should never be taken.#Warn(Message); with Message a constant fails with error: '#Warn' message must be a string literal. Write the text in place.Inside a body,
#Warn("…"); without importing it fails with error: name '#Warn' is not defined in this scope. Add it to the import Core::{ … } line.#Allow("naming.konst") stops the build with error: unknown lint rule 'naming.konst'; valid rules are: naming.type, naming.const, docs.missing.Try it yourself
- Flip the condition to
when #target.pointerBits >= 64and build. Then flip it back. - Delete the
#Allowline, runrux lint, and read the suggestion. - Replace the
#WarnonAveragewith#Error("Average was removed; call AverageRounded instead"). Where does the build stop? Change the call to make it build again. - Add a statement
#Warn("remember to update the scores table");at the top ofMain, and the import it needs.