Variables, types and expressions
Around 240 BC, Eratosthenes measured the whole Earth with a stick, a shadow and a little arithmetic. It took programming a while to catch up, but here is his calculation in Cone, and with it the basics of naming values, their types, and computing new ones.
mod earth;
import stdio use *;
// A constant belongs to the whole module: the distance from Syene
// to Alexandria, in km. The d makes 800d a 64-bit floating-point
// number, whose type is f64
const syeneToAlexandria = 800d;
fn main() {
// At noon in Syene the sun stood straight overhead, while in
// Alexandria a stick's shadow leaned 7.2 degrees.
// let: this variable can never be changed
let shadow = 7.2d;
// The two cities are one slice of the whole 360-degree circle
let slices = 360d / shadow;
// A variable can be declared with its type,
// and assigned its value later, with =
let earth f64;
earth = slices * syeneToAlexandria;
print <- "The Earth is about ", earth, " km around\n";
// Today's measure is 40,075 km. How close did he come?
let off = (40075d - earth) / 40075d * 100d;
print <- "He was off by ", off, " percent\n";
// var: this variable may be changed. It starts as a day on foot...
var walked = 30d;
// ...then *= multiplies it by 365, changing it to a year on foot
walked *= 365d;
print <- "A year's walk covers ", walked / earth * 100d, " percent of it\n";
}
It prints:
The Earth is about 40000 km around He was off by 0.187149 percent A year's walk covers 27.375 percent of it
Try this: change the shadow to 7.0d, and see how much a fifth of a degree mattered to his answer.
let and var#
A variable holds a value, and every declaration says one thing more: whether the variable may be given a new value. let says it never will. shadow holds 7.2 for as long as it exists, and any attempt to assign it another value is refused before the program ever runs. var says it may: walked starts as a day's walk and is then changed to a year's.
Most variables in a program never need to change, so writing let for them makes the few that do stand out. A let variable may still be declared first and assigned its value later, once, as earth is.
let and var speak only of the name. What may be done through a reference to a value, whether to read it, change it or share it, is a separate question, answered by a permission (imm, mut and the others) written on the reference. The Memory part shows how far that idea goes.
Every value has a type#
Every value has a type, known when the program is compiled. Usually the compiler works it out from the value: shadow is an f64 because 7.2d is. Or the type can be written after the name, as let earth f64 does.
The number types say their size in their name: i8 to i64 are signed integers, u8 to u64 unsigned ones, and f32 and f64 floating point. A whole number with no suffix is an i32, and one with a decimal point an f32; a suffix says otherwise: d for f64, as above, or u8, i64 and the rest.
Types are strict: a value crosses from one type to another only when the program asks. A whole number widens to a bigger whole number by itself, an i32 into an i64, since nothing can be lost. But to use one as a float, ask for it by the type you want: f64.from(days). Going back, i32.from(x) drops a float's fraction. Numbers has the number types in full, and the pages after it bring in text and the types you make yourself.
Constants#
A const names a value fixed when the program is compiled. Declared outside any function, as syeneToAlexandria is, it belongs to the whole module, and every function can use it. A variable declared inside a function belongs to that function alone. Named constants has what a constant may be computed from.
Computing new values#
Expressions use the operators you would expect, with the usual precedence: * and / before + and -, and parentheses to say otherwise, as in the calculation of off. A var variable can be updated in place: walked *= 365d is walked = walked * 365d, and +=, -= and /= work the same way. Dividing two integers gives a whole number, rounding toward zero. Operators has them all.
print takes several values, separated by commas, and sends each in turn; a number goes out as its digits.
Every statement that does not end with a block ends with a semicolon, even the last one before a closing brace. A statement may run over as many lines as it needs, since only the semicolon ends it.
You can now name values, say which may change, and compute with them. Next come blocks, which group statements and give a value of their own: the first step into control flow.