Let's begin with literals and variables, two basic building blocks for any expression. Along the way, additional concepts will be introduced, such as types, permissions, variable declarations, assignment, and scope.
A literal is a specific, unchangeable value. Each of these numbers is a literal:
123 3.14159 1e6 // 1000000.0 -1_000_000 // underscores are ignored 0x1000 // Hexadecimal 1p10 // 1024. (power-of-two exponent)
Beyond numbers, other forms of literals will be introduced later, including:
- string and character literals (e.g., "Hello" and 'a')
- boolean true and false
- null value for a nullable reference
- struct, array, and other compound literals
Every value has a type it belongs to. Although we will cover types more thoroughly later, let's simply say for now that each type corresponds to a specific collection of allowed values. Thus, the Bool type only supports true and false as values. Integer types only support integer number values. Floating point types support floating point number values. ... and so on.
Each type also establishes what operations can be performed on values of that type. Thus, an integer type permits two integers to be added together, the result of which is another integer value.
For the next few pages, examples will only use Cone's built-in number types, which specify their bit-size as part of the type's name. Here they are:
- Signed integer types: i8, i16, i32 and i64
- Unsigned integer types: u8, u16, u32 and u64
- Floating point types: f32 and f64
Typed Number Literals
To explicitly specify the type for a numeric literal, append the type name right after the number:
65u8 // 8-bit unsigned integer 1f64 // 64-bit floating point number
If the type is omitted, the number is understood to be 32-bits. When the number explicitly includes a decimal point or exponent, it is treated as a floating point number (f32). Otherwise, it is considered to be a signed integer (i32).
3.14159 // f32 (because of the decimal point) 1e6 // f32 (because of the exponent) 1_000_000 // i32 (no decimal point or exponent)
Type name shortcuts are also supported:
1000u // u32 1f // f32 1d // f64
A variable is a named container which holds a value of some specific type.
Before a variable can be used, it must first be declared and initialized. This example explicitly does both:
imm height f32 = 1.86
Let's examine each part of this variable declaration statement:
- imm at the beginning announces that this statement is a variable declaration. It also indicates that the variable is immutable: once given its initial value, it can never be given a different value.
- height is the name of the variable. A variable name is an identifier which typically begins with an alphabetic letter, followed by letters or numbers. Underscores are also permissible.
- f32 is the variable's declared type. The variable can only hold values of this type.
- Following the = is an expression that establishes the initial value for the variable. In this example, height's value is 1.86.
When an initial value is specified, the type can usually be omitted. It will be inferred, as this example shows:
imm height = 1.86 // f32 type is inferred imm width = height // its value is 1.86 and type is f32
Notice that the second statement makes use of the variable 'height' after it has been declared and initialized.
It is often preferable to initialize a variable as part of declaration. When that is not realistic, initialization may be performed later using an assignment:
imm height f32 // ... some statements here ... height = 1.86 // Initialize height now
An assignment essentially copies the evaluated value of the expression to the right of the = into the value container specified to the left. In this case, the value container is a variable. Other sorts of containers are possible and will be described later. Any assignment is itself an expression, and may be used wherever expressions are allowed.
It is worth pointing out that any attempt to re-assign 'height' later to another value will trigger an error message:
height = 2.12 // **ERROR** since height is immutable and initialized
What if we need to change a variable's value after initialization? To allow that, use mut (instead of imm) on the declaration statement:
mut age = 23 // type is i32 // ... some statements here ... age = 24 // alters age to hold 24 instead
mut and imm are called permissions. Cone supports a number of other permissions. These are discussed more fully on the Permissions page.
Note: If we had tried to re-assign age to 24.0, we would have gotten an error message. This is because age is declared to only hold integer values, and 24.0 is a floating point number. Type safety of variables requires that the expression's type be the same or coerceable.
Scope and Visibility
As we have seen, variables have a name, permission, type and value. They also have a scope and visibility. Scope and visibility restrict (isolate) where a variable may be referenced and used in a program.
- Scope is established by where the variable's declaration is found. Based on this position, the scope can be global, local to a function, local to a block, or a field in a type.
- Visibility is determined by the variable name. If the name starts with an underscore, it is private. Otherwise, it is public. Visibility determines whether a global variable or type field is visible outside its owning namespace.