Compile-time code, 2 of 4
Attributes
Mark declarations with the built-in attributes, and declare attributes of your own that generic code reads.
What you’ll build
First, a few declarations marked with built-in attributes, which change how the compiler treats
them. Then three attributes of your own, Label, Range, and Secret, on a Player Struct’s
fields, and a Describe function that reads them to show a Player as readable text.
Built-in attributes
An attribute starts with @ and goes before a declaration, on its line or on the line above.
Import std.core
@Flags Enum Access
READ
WRITE
RUN
End Enum
@Deprecated("use Area")
Function Size(w: Int, h: Int): Int => w * h
Function Area(w: Int, h: Int): Int => w * h
Local rights = Access.READ | Access.WRITE
Print(rights)
Print(rights.Has(Access.WRITE), rights.Has(Access.RUN))
Print(Access.RUN As Int)
Print(Size(2, 3))READ | WRITE True False 4 6
@Flagsmakes an Enum’s members powers of two, which|combines andHastests.@Deprecated("use Area")keepsSizeworking, and warns at each use outside its own code:warning: 'Size' is deprecated: use Area.
The others:
| Attribute | What it does |
|---|---|
@Inline | inlines every call to the function |
@Open | lets an Enum hold any value of its integer type, not only its members |
@PanicsAtCaller | reports the function’s panics at the line that called it |
@MainThread | lets only the main thread call the function |
@CLayout | lays a Struct out as C does |
@Test | makes a function a test, which fb test runs and builds leave out |
@PanicsAtCaller
A panic normally reports the line it happens on. For a function that panics when it’s misused,
the useful line is the caller’s, where the mistake is. @PanicsAtCaller reports that one.
Import std.core
@PanicsAtCaller
Function Percent(part: Int, whole: Int): Int
If whole == 0 Then Panic("Percent of a whole of 0")
Return part * 100 / whole
End Function
Print(Percent(1, 4))
Print(Percent(1, 0))25
panic: Percent of a whole of 0 at main.fb:10:7
at Percent (main.fb:5)
at top-level code (main.fb:10)The panic names line 10, the second call, and exits with code 101, as every panic does.
Declaring attributes
Attribute Name(parameters) declares an attribute of your own. Its parameters are numbers, Bools,
Strings, Chars, or Enums, and can have defaults. A use passes its arguments as a call does, by
position or by name, and each must be known at compile time.
Import std.core
Attribute Label(Text: String)
Attribute Range(Low: Int = 0, High: Int = 100)
Attribute Secret
Struct Player
@Label("Name")
Field Name: String
@Label("Health points") @Range(High: 200)
Field Health: Int
@Range(1, 50)
Field Level: Int
@Secret
Field Password: String
End Struct
Local ada = Player("Ada", 90, 3, "hunter2")
Print(ada.Name)Ada
An attribute changes nothing by itself. A use without an argument its parameter needs is an
error, missing an argument for parameter 'Text', and so is the same attribute written twice on
one declaration.
Reading attributes
Generic code reads attributes through compile-time reflection. For Field f: F In T repeats its
body for each of a type’s fields, with F the field’s type. Its Where F: Printable line says
each field’s type must be Printable, so that f.Get(value), which reads the field, can go in a
string. f.Has(Secret) tests for an attribute, and f.Attribute(Label) gives a Label?, Null
when the field has none.
Import std.core
Import std.text
Attribute Label(Text: String)
Attribute Range(Low: Int = 0, High: Int = 100)
Attribute Secret
Struct Player
@Label("Name")
Field Name: String
@Label("Health points") @Range(High: 200)
Field Health: Int
@Range(1, 50)
Field Level: Int
@Secret
Field Password: String
End Struct
Function Describe<T>(value: T): String
Local lines: String[] = []
For Field f: F In T
Where F: Printable
If f.Has(Secret) Then Continue
Local line = "{f.Attribute(Label)?.Text ?? f.Name}: {f.Get(value)}"
Local range = f.Attribute(Range)
If range != Null Then line += " ({range.Low} to {range.High})"
lines.Add(line)
End For
Return "\n".Join(lines)
End Function
Print(Describe(Player("Ada", 90, 3, "hunter2")))Name: Ada Health points: 90 (0 to 200) Level: 3 (1 to 50)
The Password is never printed, and Level, with no Label, shows its field’s name. std uses
attributes the same way: std.core’s @Rename and @Skip change how Load reads and Save writes a
field,
and std.os’s @Option and @Flag describe a command line.
Reflection covers the loop in full.
What you learned
- Built-in attributes, like
@Flags,@Deprecated, and@PanicsAtCaller, change how the compiler treats a declaration. Attribute Name(parameters)declares an attribute, whose arguments are known at compile time.f.Has(A)andf.Attribute(A)read a field’s attributes in aFor Fieldloop.- An attribute does nothing by itself; code that reads it gives it meaning.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.