Cone Language Reference

Literals, Variables and Assignment

On this page

We 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.

Literals#

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

Beyond numbers, other forms of literals will be introduced later, including:

  • string and character literals (e.g., "Hello" and 'a')
  • boolean true and false
  • struct, array, and other compound values

Named constants#

Constant literals can be given a name. For example:

const pi = 3.141592;

With this declaration in place, any program reference to the name pi will substitute in the the corresponding floating-point constant literal value.

A constant's value may be an expression computed from constants alone: number literals, true and false, and other named constants, declared before it or after, combined by the arithmetic, bitwise and shift operators (+ - * / % | & ^ ~ << >>, and a prefix -), the comparisons, not, and and or, a number's conversion T.from(x), a reinterpretation with as, and parentheses. The constant is the literal the expression computes:

const Red = 0x1u32;
const Green = 0x2u32;
const All = Red | Green | Blue;    // 0x7u32
const Blue = 0x4u32;
const Half = 1.0f32 / 2.0f32;      // 0.5f32

Each operator gives the value it gives when the program runs, on the same type: an f32 is computed as an f32, integer division truncates toward zero, and << drops the bits shifted out. What has no value when the program runs is an error: a result its type cannot hold (255u8 + 1u8), a division or remainder by zero, and a float converted to an integer that cannot hold it. So is a shift by the type's width or more, or by a negative amount, although the running program gives one a value (Shift operators). So is a constant whose value names itself, directly or through other constants. A function call, a variable or a method like sqrt is not a constant, so neither is an expression using one. The same expressions may be written wherever a constant is required: a global variable's or a static's value, a parameter's or a field's default, and the elements of an array, struct or tuple literal in any of these.

A constant may name a compound literal too, such as an array, a struct or a tuple. Each use of it is that literal, so it may be used however the literal could be:

const primes Array[u32, 4] = [2u32, 3u32, 5u32, 7u32];

primes[n]          // an element, by a constant or run-time index
&primes            // a reference to the array, as &[2u32, 3u32, 5u32, 7u32] is

Note: Named constants of numbers, strings, arrays, structs and tuples are implemented, and so are the constant expressions above. A constant's elements and fields may be read, and a constant array or string, which is kept in read-only memory as its literal is, may be borrowed: as a reference or a slice, never mut, living as long as the program. No other constant may be borrowed, nor may a field of one or of one of its elements, since each use of it is a fresh copy with no place in memory, as its literal would be; that includes calling a method that takes a borrowed self on one.

Types#

Every value has a type it belongs to. A type is a pattern that:

  • Establishes all permissible 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.
  • Determines how values are digitally encoded, including how many binary bits are needed to encode them.
  • Specifies what operations can be performed on values of that type. For example, an integer type permits two integers to be added together, the result of which is another integer value. These type operations are called methods.
  • Enforces constraints that ensure the type's values are handled consistently and safely.

Every value in a program has a type, known at compile-time due to an explicit declaration or because the type can be easily inferred based on the context. By knowing the types of all values, the compiler can optimize a program's performance and memory use. Clear typing facilitates precise, flexible data manipulation and improves the safe and correct use of data.

The names of specific types are capitalized by convention. This helps cleanly distinguish variable names from type names, reducing unexpected naming conflicts and improving code clarity. One notable exception to this naming convention is the type names for the primitive numeric types.

Number types#

For the next few pages, examples will only use the 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

Later, a number of other types will be introduced.

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

Variable#

A variable is like a named container which holds a value of some specific type.

Variable declarations#

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.

Delayed initialization#

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 variable specified to the left. (Later, we will show that assignment can also be used to place values into structures that lie "within" a variable's value.) 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

Mutable Variables#

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.

Static variables#

A variable declared inside a function is ordinarily created afresh on every call and released when the call ends. A variable declared static is one copy instead, shared by every call of the function, initialized once before the program starts running, and kept across calls:

fn tick() i32 {
  static mut calls = 0;
  calls = calls + 1;   // 1 on the first call, 2 on the second, ...
  calls;
}

The same word means the same thing at every scale. Inside a type, a static variable is one copy shared by every value of that type, rather than a field held by each value. At the top of a module, it is the module's one copy, which is what a global variable is already. A generic type or module has one copy per instantiation: each instantiation, such as Box[i32], is a type or module in its own right with its own static, since the static's type may name the generic parameter. Because a static's initial value is written into its storage before anything runs, it must be a literal, as a global variable's must.

static applies to a variable and to nothing else: a function has no copy per call to share, and neither does a type or a statement.

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 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 declared, not spelled: a global variable or type field is private unless its declaration begins with pub. Visibility determines whether a global variable or type field is visible outside its owning namespace.