This is the reference manual for the Stellar programming language.
Stellar is a general-purpose language designed with systems programming in mind. It is strongly typed and garbage-collected.
The syntax is specified using a variant of Extended Backus-Naur Form (EBNF):
Syntax = { Production } .
Production = production_name "=" [ Expression ] "." .
Expression = Term { "|" Term } .
Term = Factor { Factor } .
Factor = production_name | token [ "…" token ] | Group | Option | Repetition .
Group = "(" Expression ")" .
Option = "[" Expression "]" .
Repetition = "{" Expression "}" .
Productions are expressions constructed from terms and the following operators, in increasing precedence:
| alternation
() grouping
[] option (0 or 1 times)
{} repetition (0 to n times)
Lowercase production names are used to identify lexical (terminal) tokens. Non-terminals are in CamelCase. Lexical tokens are enclosed in double quotes “” or back quotes ``.
The form a … b
represents the set of characters from a through b as alternatives. The horizontal ellipsis …
is also used elsewhere in the spec to informally denote various enumerations or code snippets that are not further specified. The character …
(as opposed to the three characters ...
) is not a token of the Stellar language.
Source code is Unicode text encoded in UTF-8. The text is not canonicalized, so a single accented code point is distinct from the same character constructed from combining an accent and a letter; those are treated as two code points. For simplicity, this document will use the unqualified term character to refer to a Unicode code point in the source text.
Each code point is distinct; for instance, uppercase and lowercase letters are different characters.
The following terms are used to denote specific Unicode character categories:
newline = /* the Unicode code point U+000A */ .
unicode_char = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point categorized as "Letter" */ .
unicode_digit = /* a Unicode code point categorized as "Number, decimal digit" */ .
In The Unicode Standard 8.0, Section 4.5 “General Category” defines a set of character categories. Stellar treats all characters in any of the Letter categories Lu
, Ll
, Lt
, Lm
, or Lo
as Unicode letters, and those in the Number category Nd
as Unicode digits.
NOTE: The underscore character
_
(U+005F
) is not considered a letter.
letter = unicode_letter | "*" .
decimal_digit = "0" … "9" .
binary_digit = "0" | "1" .
octal_digit = "0" … "7" .
hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
Comments serve as program documentation and start with //
.
NOTE:
A comment cannot start inside a char or string literal, or inside another comment.
Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
letter_or_underscore = letter | "_" .
identifier = letter { letter_or_underscore | unicode_digit }
| "_" ( letter_or_underscore | unicode_digit )
{ letter_or_underscore | unicode_digit } .
a
_x9
ThisVariableIsExported
αβ
NOTE: single
_
character is not considered an identifier, but rather a punctuation.
Some identifiers are predeclared.
The following keywords are reserved and may not be used as identifiers.
as defer else enum for false fun if pub return struct
true type let where while match import break continue
dyn loop interface implements
The following character sequences represent operators (including assignment operators) and punctuation:
-> & &= && * ** *= @ ! } ] ) : , . .. = ==
> >= << < <= - -= -- ~ != { [ ( | |= || %
%= + += ++ ? >> ; / /= ^ ^= # _
An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: 0b
or 0B
for binary, 0
, 0o
, or 0O
for octal, and 0x
or 0X
for hexadecimal. A single 0
is considered a decimal zero. In hexadecimal literals, letters a
through f
and A
through F
represent values 10
through 15
.
For readability, an underscore character _
may appear after a base prefix or between successive digits; such underscores do not change the literal’s value.
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits .
decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits = binary_digit { [ "_" ] binary_digit } .
octal_digits = octal_digit { [ "_" ] octal_digit } .
hex_digits = hex_digit { [ "_" ] hex_digit } .
42
4_2
0600
0_600
0o600
0O600 // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727
_42 // an identifier, not an integer literal
42_ // invalid: `_` must separate successive digits
4__2 // invalid: `_` must separate successive digits
0_xBadFace // invalid: `_` must separate successive digits
A floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (e
or E
followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by 10exp
.
For readability, an underscore character '_'
may appear after a base prefix or between successive digits; such underscores do not change the literal value.
float_lit = decimal_digits "." [ decimal_digits ] [ exponent ] |
decimal_digits exponent |
"." decimal_digits [ exponent ] .
exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40 // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5. // == 15.0
0.15e+0_2 // == 15.0
1_.5 // invalid: `_` must separate successive digits 1._5
1._5 // invalid: `_` must separate successive digits
1.5_e1 // invalid: `_` must separate successive digits
1.5e_1 // invalid: `_` must separate successive digits
1.5e1_ // invalid: `_` must separate successive digits
A character literal represents a character constant, an integer value identifying a Unicode code point. A character literal is expressed as one or more characters enclosed in single quotes, as in 'x'
or '\n'
. Within the quotes, any character may appear except newline and unescaped single quote. A single quoted character represents the Unicode value of the character itself, while multi-character sequences beginning with a backslash encode values in various formats.
The simplest form represents the single character within the quotes; since Stellar source text is Unicode characters encoded in UTF-8, multiple UTF-8-encoded bytes may represent a single integer value. For instance, the literal 'a'
holds a single byte representing a literal a
, Unicode U+0061
, value 0x61
, while 'ä'
holds two bytes (0xc3
0xa4
) representing a literal a-dieresis, U+00E4
, value 0xe4
.
Several backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: \x
followed by exactly two hexadecimal digits; \u
followed by exactly four hexadecimal digits; \U
followed by exactly eight hexadecimal digits, and a plain backslash \
followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
Although these representations all result in an integer, they have different valid ranges. Octal escapes must represent a value between 0
and 255
inclusive. Hexadecimal escapes satisfy this condition by construction. The escapes \u
and \U
represent Unicode code points so within them some values are illegal, in particular those above 0x10FFFF
and surrogate halves.
After a backslash, certain single-character escapes represent special values:
char_lit = "'" ( unicode_value | byte_value ) "'" .
unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value = `\` "x" hex_digit hex_digit .
little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
hex_digit hex_digit hex_digit hex_digit .
escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''
'aa' // illegal: too many characters
'\k' // illegal: k is not recognized after a backslash
'\xa' // illegal: too few hexadecimal digits
'\0' // illegal: too few octal digits
'\400' // illegal: octal value over 255
'\uDFFF' // illegal: surrogate half
'\U00110000' // illegal: invalid Unicode code point
A type alias defines a new name for an existing type. Type aliases are declared with the keyword type
:
TypeAlias = "type" identifier [ GenericParameters ] "=" Type .
For example, the following defines the type Point
as a synonym for the type (uint8, uint8)
, the type of pairs of unsigned 8 bit integers:
type Point = (uint8, uint8);
fun main() {
let point: Point = (1, 2);
}
NOTE: Type aliases cannot be used to qualify type’s constructor:
struct A(uint32); type B = A; fun main() { let a = A(42); let a = B(42); // invalid }
NOTE: Type aliases cannot be used to qualify interfaces:
type MyToString = ToString; // invalid fun foo[T](s: S) where S: MyToString {}
NOTE: Type aliases cannot be used to call static methods on:
type MyToString = String; fun main() { let s = "hello"; println(MyToString.len(s)); // invalid println(String.len(s)); // ok }
A function consists of a block, along with a name, a set of parameters, and an output type. Other than a name, all these are optional. Functions are declared with the keyword fun
. Functions may declare a set of input variables as parameters, through which the caller passes arguments into the function, and the output type of the value the function will return to its caller on completion. If the output type is not explicitly stated, it is the unit type.
Function = [ "pub" ] "fun" identifier "[" GenericParameters "]" "(" FunctionParameters ")"
[ ":" Type ] [ WhereClause ] StatementsBlock
| [ "pub" ] "fun" identifier "[" GenericParameters "]" "(" FunctionParameters ")"
[ ":" Type ] [ WhereClause ] ";" .
FunctionParameters = [ FunctionParameter { "," FunctionParameter } [ "," ] ] .
FunctionParameter = Pattern ":" Type
| "self" [ ":" Type ] .
Example:
fun answer_to_life_the_universe_and_everything(): uint32 {
42
}
Function parameters are irrefutable patterns, so any pattern that is valid in an else-less let binding is also valid as a parameter:
fun first((value, _): (int32, int32)): int32 { value }
If the first parameter is a self
, this indicates that the function is a method.
Method = Function .
A generic function allows one or more parameterized types to appear in its signature. Each type parameter must be explicitly declared in an bracket-enclosed and comma-separated list, following the function name.
fun foo[A, B](a: A, b: B) where A: ToString { ... }
NOTE: Function overloading is not supported in Stellar.
fun foo(A { a }: A) {} fun foo() {} // invalid
NOTE: Functions with names
_
cannot exist, because_
is not a valid identifier.fun _() { println("test") }
Struct = StructStruct | TupleStruct .
StructStruct = [ "pub" ] "struct" identifier "[" GenericParameters "]"
[ Implements ] [ WhereClause ] "{" StructFieldList { Method } "}" .
Implements = "implements" TypeConstructor { "," TypeConstructor } [ "," ] .
StructFields = [ StructField { "," StructField } [ "," ] ] .
StructField = [ "pub" ] identifier ":" type .
TupleStruct = [ "pub" ] "struct" identifier "[" GenericParameters "]"
"(" TupleFields ")" [ Implements ] [ WhereClause ]
"{" { Method } "}" .
TupleFields = [ TupleField { "," TupleField } [ "," ] ] .
TupleField = [ "pub" ] Type .
A struct is a nominal struct type defined with the keyword struct
.
An example of a struct module item and its use:
struct Point {
x: int32,
y: int32
fun new(x: int32, y: int32): Point {
Point { x, y }
}
}
fun main() {
let point = Point.new(1, 2);
}
A tuple struct is a nominal tuple type, also defined with the keyword struct
. For example:
struct Point(int32, int32) {
fun new(x: int32, y: int32): Self {
Self(x, y)
}
}
fun main() {
let point = Point.new(1, 2);
let x = point.0;
}
Enum = [ "pub" ] "enum" identifier "[" GenericParameters "]"
[ WhereClause ] "{" EnumItems { Method } "}" .
EnumItems = [ EnumItem { "," Enumitem } [ "," ] ] .
EnumItem = identifier
| identifier "{" StructFields "}"
| identifier "(" TupleFields ")" .
An enumeration, also referred to as an enum, is a simultaneous definition of a nominal enumerated type as well as a set of constructors, that can be used to create or pattern-match values of the corresponding enumerated type.
Enumerations are declared with the keyword enum
.
An example of an enum item and its use:
enum Animal {
Dog,
Cat
}
fun main() {
let a: Animal = Animal.Dog;
a = Animal.Cat;
}
Enum constructors can have either named or unnamed fields:
enum Animal {
Dog(String, float64),
Cat { name: String, weight: float64 },
}
fun main() {
let a: Animal = Animal.Dog("Cocoa", 37.2);
a = Animal.Cat { name: "Spotty", weight: 2.7 };
}
In this example, Cat
is a struct-like enum variant, whereas Dog
is simply called an enum variant.
An enum where no constructors contain fields are called a field-less enum. For example, this is a fieldless enum:
enum Fieldless {
Tuple(),
Struct{},
Unit,
}
NOTE: Enum items don’t have visibilities!
pub enum Option[T] { pub None, // invalid Some(T), }
Interface = [ "pub" ] "interface" identifier "[" GenericParameters "]"
[ ":" Bounds ] [ WhereClause ] "{" { Method } "}" .
Interfaces are declared with the keyword interface
.
Interface methods may omit the function body by replacing it with a semicolon. This indicates that the implementation must define the method’s body. If the interface method defines a body, this definition acts as a default for any implementation which does not override it.
interface ToString {
fun to_string(self): String;
}
Type parameters can be specified for a interface to make it generic. These appear after the interface name, using the same syntax used in generic functions:
interface Iterator[T] {
fun next(self): Option[T]
}
Super interfaces are interfaces that are required to be implemented for a type to implement a specific interface. Furthermore, anywhere a generic or interface object is bounded by a interface, it has access to the associated items of its super interfaces.
Super interfaces are declared by bounds on the Self
type of an interface and transitively the super interfaces of the interfaces declared in those bounds. It is an error for an interface to to be its own super interface.
The interface with a super interface is called a sub interface of its super interface.
The following is an example of declaring Shape
to be a super interface of Circle
.
interface Shape { fun area(self): float64; }
interface Circle : Shape { fun radius(self): float64; }
And the following is the same example, except using where clauses.
interface Shape { fun area(self): float64; }
interface Circle where Self: Shape { fun radius(self): float64; }
This next example gives radius a default implementation using the area
function from Shape
.
interface Circle where Self: Shape {
fun radius(self): float64 {
// A = pi * r^2
// so algebraically,
// r = sqrt(A / pi)
(self.area() /std.float64.consts.PI).sqrt()
}
}
This next example calls a super interface method on a generic parameter.
fun print_area_and_radius[C: Circle](c: C) {
println(c.area());
println(c.radius());
}
Similarly, here is an example of calling super interface methods on interface objects.
let circle = circle as dyn Circle;
let nonsense = circle.radius() * circle.area();
Function or method declarations without a body only allow identifier or _
wild card patterns. All irrefutable patterns are allowed as long as there is a body:
interface T {
fun f1((a, b): (int32, int32)) {}
fun f2(_: (int32, int32));
fun f3((a, b): (int32, int32)) {} // invalid
}
All methods in public interface are public. All method in private interface are private! So pub
in methods is invalid!
pub interface Foo {
pub fun foo(); // invalid
}
Import = "import" ImportPath .
ImportPath = Path [ "as" identifier ] .
Imports are used to qualify long names from other modules, packages, etc.
Examples:
import std.io;
import std.fs as stdfs;
Statement = `;` /* empty statement */
| LetStatement
| ExpressionStatement
| DeferStatement
| ContinueStatement
| BreakStatement .
LetStatement = "let" Pattern [ ":" Type ] "=" Expression ";" .
A let statement introduces a new set of variables, given by a pattern. The pattern is followed optionally by a type annotation and then either ends, or is followed by an initializer expression plus an optional else block. When no type annotation is given, the compiler will infer the type, or signal an error if insufficient type information is available for definite inference.
Any variables introduced by a variable declaration are visible from the point of declaration until the end of the enclosing block scope, except when they are shadowed by another variable declaration.
The pattern inside the let statement must be irrefutable.
let [a, b] = [1, 2];
let (a, b, _) = (1, 2, 3);
let a = 3;
let Some(a) = foo(); // invalid
ExpressionStatement = ExpressionWithoutBlock ";"
| ExpressionWithBlock [ ";" ] .
An expression statement is one that evaluates an expression and ignores its result. As a rule, an expression statement’s purpose is to trigger the effects of evaluating its expression.
An expression that consists of only a block expression or control flow expression, if used in a context where a statement is permitted, can omit the trailing semicolon. This can cause an ambiguity between it being parsed as a standalone statement and as a part of another expression; in this case, it is parsed as a statement.
v.pop();
if v.is_empty() {
v.push(5);
} else {
v.remove(0);
}
3 + 2; // separate expression statement
When the trailing semicolon is omitted, the result must be type ()
.
fun foo(): int32 {
if true {
1
} else {
2
}; // invalid
}
DeferStatement = "defer" Expression ";" .
Defer statements are used to defer the execution of a function until the end of the enclosing block scope and are denoted with the keyword defer
:
defer file.close();
defer { println("deferred") };
The expression in the defer statement must be either a call or a block.
ReturnStatement = "return" Expression ";" .
Return statements are denoted with the keyword return
. Evaluating a return expression moves its argument into the designated output location for the current function call, destroys the current function activation frame, and transfers control to the caller frame:
fun factorial(n: uint32): uint32 {
if n < 2 {
1
} else {
n * factorial(n - 1)
}
}
BreakStatement = "break" ";" .
Break statements are denoted with the keyword break
. When break is encountered, execution of the associated loop body is immediately terminated, for example:
fun main() {
let a = 3;
loop {
a++;
if a > 10 {
break;
}
}
println(a);
}
ContinueStatement = "continue" ";" .
Continue statements are denoted with the keyword continue
. When continue is encountered, the current iteration of the associated loop body is immediately terminated, returning control to the loop head. In the case of a while loop, the head is the conditional expression controlling the loop. In the case of a for loop, the head is the call-expression controlling the loop.
fun main() {
let a = 0;
loop {
a++;
if a > 4 {
continue;
}
println(a); // prints 1\n2\n3\n4\n
}
}
LiteralExpression = "true" | "false" | int_lit | float_lit
| string_lit | char_lit .
A literal expression is an expression consisting of a single token, rather than a sequence of tokens, that immediately and directly denotes the value it evaluates to, rather than referring to it by name or some other evaluation rule.
A literal is a form of constant expression, so is evaluated (primarily) at compile time.
Each of the lexical literal forms described earlier can make up a literal expression, as can the keywords true
and false
.
BlockExpression = StatementsBlock .
StatementsBlock = "{" Statements "}" .
Statements = [ Statement { "," Statement } [ "," ] ] .
A block expression, or block, is a control flow expression and anonymous namespace scope for items and variable declarations. As a control flow expression, a block sequentially executes its component non-item declaration statements and then its final optional expression. As an anonymous namespace scope, variables declared by let statements are in scope from the next statement until the end of the block.
let a = {
let b = 3;
b++;
b
};
BinaryExpression = Expression BinaryOperator Expression ";" .
BinaryOperator = "+=" | "+" | "-=" | "-" | "**" | "*" | "*="
| "/=" | "/" | "!=" | ">>" | "<<" | "<="
| "<" | ">=" | ">" | "==" | "=" | "|" | "&"
| "||" | "&&" | "|=" | "&=" | "%" | "%=" .
PrefixExpression = PrefixOperator Expression .
PrefixOperator = "++" | "--" .
PostfixExpression = Expression PostfixOperator .
PostfixOperator = "++" | "--" .
ParenthesizedExpression = "(" Expression ")" .
A parenthesized expression wraps a single expression, evaluating to that expression. The syntax for a parenthesized expression is a (
, then an expression, called the enclosed operand, and then a )
.
Parenthesized expressions evaluate to the value of the enclosed operand. Parentheses can be used to explicitly modify the precedence order of subexpressions within an expression.
An example of a parenthesized expression:
fun main() {
let x = 2 + 3 * 4;
let y = (2 + 3) * 4;
}
ListExpression = "[" [ Expression { "," Expression } [ "," ] ] "]" .
List expressions construct lists. The syntax is a comma-separated list of expressions of uniform type enclosed in square brackets. This produces an list containing each of these values in the order they are written.
let x = [1, 2, 3];
let y = ["a", "b", "c"];
let empty = [];
TupleExpression = "(" ExpressionsInTuple ")" .
ExpressionsInTuple = /* empty */
| Expression ","
| Expression "," Expression { "," Expression } [ "," ] .
A tuple expression constructs tuple values.
The syntax for tuple expressions is a parenthesized, comma separated list of expressions, called the tuple initializer operands. 1-ary tuple expressions require a comma after their tuple initializer operand to be disambiguated with a parenthetical expression.
Tuple expressions are a value expression that evaluate into a newly constructed value of a tuple type. The number of tuple initializer operands is the arity of the constructed tuple. Tuple expressions without any tuple initializer operands produce the unit tuple. For other tuple expressions, the first written tuple initializer operand initializes the field 0
, and subsequent operands initializes the next highest field. For example, in the tuple expression ('a', 'b', 'c')
, 'a'
initializes the value of the field 0
, 'b'
field 1
, and 'c'
field 2
.
Examples of tuple expressions and their types:
Expression | Type |
---|---|
() |
() (unit type) |
(0.0, 4.5) |
(float64, float64) |
("x",) |
(String,) |
("a", (1,), true) |
(String, (int32,), bool) |
CastExpression = Expression "as" Type .
A type cast expression is denoted with the binary operator as
.
Executing an as
expression casts the value on the left-hand side to the type on the right-hand side.
An example of an as
expression:
let x = 1 as bool;
A table of all possible type casts:
From | To | Cast | Example |
---|---|---|---|
Numeric type | Numeric type | Numeric cast | 1 as uint64 |
Enumeration | Integer type | Enum cast | Color.Red as int32 |
bool or char |
Integer type | Primitive to integer cast | true as int32 |
Integer type | bool or char |
Integer to primitive cast | 0 as bool |
A |
A |
Type to itself cast | "hello" as String |
A |
dyn T where A: T |
Cast to interface object | 1 as dyn ToString |
LoopExpression = "loop" StatementsBlock .
A loop
expression repeats execution of its body continuously: loop { println("hi!"); }
.
WhileExpression = "while" ExpressionExceptStruct StatementsBlock .
A while loop begins by evaluating the boolean loop conditional operand. If the loop conditional operand evaluates to true, the loop body block executes, then control returns to the loop conditional operand. If the loop conditional expression evaluates to false, the while expression completes.
An example:
let i = 0;
while i < 10 {
println("hello");
i++;
}
IfExpression = "if" ExpressionExceptStruct StatementsBlock
[ "else" (StatementsBlock | IfExpression) ] .
An if
expression is a conditional branch in program control. The syntax of an if
expression is a condition operand, followed by a consequent block, any number of else if
conditions and blocks, and an optional trailing else
block. The condition operands must have the boolean type. If a condition operand evaluates to true
, the consequent block is executed and any subsequent else if
or else
block is skipped. If a condition operand evaluates to false
, the consequent block is skipped and any subsequent else if
condition is evaluated. If all if
and else if
conditions evaluate to false
then any else
block is executed. An if expression evaluates to the same value as the executed block, or ()
if no block is evaluated. An if
expression must have the same type in all situations.
if x == 4 {
println("x is 4");
} else if x == 5 {
println("x is 5");
} else {
println("x is neither 4 nor 5");
}
let y = if 12 * 15 > 150 {
"Bigger"
} else {
"Smaller"
};
MatchExpression = "match" ExpressionExceptStruct "{" [ MatchArm { "," MatchArm } [ "," ] ] "}" .
MatchArm = Pattern "->" Expression .
A match
expression branches on a pattern. The exact form of matching that occurs depends on the pattern. A match
expression has a scrutinee expression, which is the value to compare to the patterns. The scrutinee expression and the patterns must have the same type.
An example of a match expression:
match (1, 2, 4) {
(2, ..) -> {
println("First element is 2");
}
(_, 2, _) -> {
println("Second element is 2");
}
(.., 2) -> {
println("Third element is 2");
}
_ -> {
println("I don't know where is 2");
}
}
StructExpression = Path "[" GenericArguments "]"
"{" [ StructExpressionField { "," StructExpressionField } [ "," ] ] "}" .
StructExpressionField = identifier [ ":" Expression ] .
A struct expression creates a struct, enum, or union value. It consists of a path to a struct, enum variant, or union item followed by the values for the fields of the item. There are three forms of struct expressions: struct, tuple, and unit.
The following are examples of struct expressions:
let y = 0.0;
let a = Point { x: 10.0, y };
let u = game.User { name: "Joe", age: 35, score: 100_000 };
CallExpression = Expression "(" [ Expression { "," Expression } [ "," ] ] ")" .
A call expression calls a function. The syntax of a call expression is an expression, called the function operand, followed by a parenthesized comma-separated list of expression, called the argument operands:
let a = (|| "Stellar")();
let b = add(1, 2);
UnderscoreExpression = "_" .
Underscore expressions, denoted with the symbol _
, are used to signify a placeholder in a destructuring assignment. They may only appear in the left-hand side of an assignment.
let p = (1, 2);
let a = 0;
(_, a) = p;
LiteralPattern = Literal | "-" float_lit | "-" int_lit .
Literal patterns match exactly the same value as what is created by the literal. Since negative numbers are not literals, literal patterns also accept an optional minus sign before the literal, which acts like the negation operator.
let a = 5;
match a {
5 -> {
println("a is 5");
}
2 | 4 -> {
println("a is 2 or 4");
}
_ -> {
println("a is neither 2 nor 4");
}
}
IdentifierPattern = identifier [ "@" Pattern ] .
Identifier patterns bind the value they match to a variable. The identifier must be unique within the pattern. The variable will shadow any variables of the same name in scope. The scope of the new binding depends on the context of where the pattern is used (such as match arm).
Patterns that consist of only an identifier and optionally a pattern that identifier is bound to.
let x = [2];
match x {
a @ [_, _] -> { println(a); }
a @ [_] -> { println(a); }
_ -> { println("Not matched"); }
}
WildcardPattern = "_" .
The wildcard pattern (an underscore symbol) matches any value. It is used to ignore values when they don’t matter. Inside other patterns it matches a single data field (as opposed to the ..
which matches the remaining fields).
let (a, _) = (1, x); // the x is always matched by `_`
// ignore a function/closure param
let real_part = |r: f64, _: f64| { r };
// ignore a field from a struct
let RGBA { r: red, g: green, b: blue, a: _ } = color;
RestPattern = ".." .
The rest pattern (the ..
token) acts as a variable-length pattern which matches zero or more elements that haven’t been matched already before and after. It may only be used in tuple, tuple struct, and list patterns, and may only appear once as one of the elements in those patterns. It is also allowed in an identifier pattern for list patterns only. The rest pattern is always irrefutable.
match list {
[] -> println("list is empty"),
[one] -> println("list has one element: " + one),
[head, tail @ ..] -> println("head: " + head + " tail: " + tail),
}
StructPattern = Path "[" GenericArguments "]"
"{" [ StructPatternField { "," StructPatternField } [ "," ] ] "}" .
StructPatternField = identifier [ ":" Pattern ] .
Struct patterns match struct values that match all criteria defined by its subpatterns. They are also used to destructure a struct.
let Person { name, age, .. } = get_person();
match s {
Point { x: 10, y: 20 } -> (),
Point { y: 10, x: 20 } -> (), // order doesn't matter
Point { x: 10, .. } -> (),
Point { .. } -> (),
}
TuplePattern = "(" RestPattern ")"
| "(" ")"
| "(" Pattern "," ")"
| "(" Pattern "," Pattern { "," Pattern } [ "," ] ")" .
Tuple patterns match tuple values that match all criteria defined by its subpatterns. They are also used to destructure a tuple.
The form (..)
with a single RestPattern is a special form that does not require a comma, and matches a tuple of any size.
The tuple pattern is refutable when one of its subpatterns is refutable.
let pair = (10, "ten");
let (a, b) = pair;
GroupedPattern = "(" Pattern ")" .
Enclosing a pattern in parentheses can be used to explicitly control the precedence of compound patterns.
match a {
a @ b | a @ c -> (), // same as @ (b | a @ c)
(a @ b) | a @ c -> ()
_ -> (),
}
let b: bool = true;
The boolean type or bool is a primitive data type that can take on one of two values, called true
and false
.
Values of this type may be created using a literal expression using the keywords true
and false
corresponding to the value of the same name.
b |
!b |
---|---|
true |
false |
false |
true |
a |
b |
a \| b |
---|---|---|
true |
true |
true |
true |
false |
true |
false |
true |
true |
false |
false |
false |
a |
b |
a & b |
---|---|---|
true |
true |
true |
true |
false |
false |
false |
true |
false |
false |
false |
false |
a |
b |
a ^ b |
---|---|---|
true |
true |
false |
true |
false |
true |
false |
true |
true |
false |
false |
false |
a |
b |
a == b |
---|---|---|
true |
true |
true |
true |
false |
false |
false |
true |
false |
false |
false |
true |
a |
b |
a > b |
---|---|---|
true |
true |
false |
true |
false |
true |
false |
true |
false |
false |
false |
false |
a != b
is the same as !(a == b)
a >= b
is the same as a == b | a > b
a < b
is the same as !(a >= b)
a <= b
is the same as a == b | a < b
The unsigned integer types consist of:
Type | Size in bytes | Minimum value | Maximum value |
---|---|---|---|
uint8 |
1 | 0 | 28 - 1 |
uint16 |
2 | 0 | 216 - 1 |
uint32 |
4 | 0 | 232 - 1 |
uint64 |
8 | 0 | 264 - 1 |
uint128 |
16 | 0 | 2128 - 1 |
The signed two’s complement integer types consist of:
Type | Size in bytes | Minimum value | Maximum value |
---|---|---|---|
int8 |
1 | -27 | 27 - 1 |
int16 |
2 | -215 | 215 - 1 |
int32 |
4 | -231 | 231 - 1 |
int64 |
8 | -263 | 263 - 1 |
int128 |
16 | -2127 | 2127 - 1 |
The IEEE 754-2008 “binary32” and “binary64” floating-point types are float32
and float64
, respectively.
The usize
type is an unsigned integer type with the same number of bits as the platform’s pointer type. It can represent every memory address in the process.
The isize
type is a signed integer type with the same number of bits as the platform’s pointer type. The theoretical upper bound on object and array size is the maximum isize
value. This ensures that isize
can be used to calculate differences between pointers into an object or array and can address every byte within an object along with one byte past the end.
usize
and isize
are at least 16-bits wide.
Tuple types are a family of structural types for heterogeneous lists of other types.
Structural types are always equivalent if their internal types are equivalent. For a nominal version of tuples, see tuple structs.
The syntax for a tuple type is a parenthesized, comma-separated list of types. 1-ary tuples require a comma after their element type to be disambiguated with a parenthesized type.
A tuple type has a number of fields equal to the length of the list of types. This number of fields determines the arity of the tuple. A tuple with n
fields is called an n-ary tuple. For example, a tuple with 2 fields is a 2-ary tuple.
Fields of tuples are named using increasing numeric names matching their position in the list of types. The first field is 0
. The second field is 1
. And so on. The type of each field is the type of the same position in the tuple’s list of types.
For convenience and historical reasons, the tuple type with no fields (()
) is often called unit or the unit type. Its one value is also called unit or the unit value.
Some examples of tuple types:
()
(unit type)(float64, float64)
(String, float32)
(int32, String)
(int32, (float64,), List[String], Option[bool])
Values of this type are constructed using a tuple expression. Furthermore, various expressions will produce the unit value if there is no other meaningful value for it to evaluate to. Tuple fields can be using pattern matching.