// A tour of the core language in one linear program: values and their
// widths, the array family, slices, structs, control flow as expressions,
// functions with several results, optionals and references. The other
// samples assume what this one shows.
//
// Build and run (from this directory):
// goose -o tour.c 01_tour.goose && cl tour.c && tour
// A function taking a slice accepts every array kind and any sub-range of
// one, without a copy: the slice is a reference to the elements plus a count.
fn total(xs: i64[:]) -> i64 {
var t = 0;
for x in xs { t += x; }
return t;
}
// Several results are a calling convention, not a tuple type. By custom a
// trailing bool (or error code) says whether the rest is meaningful.
fn divide(a: i64, b: i64) -> i64, bool {
if b == 0 { return 0, false; }
return a / b, true;
}
// "Maybe an element": an optional is a nullable reference into the input.
fn first_at_least(xs: i64[:], min: i64) -> i64? {
for &x in xs { if x >= min { return x; } }
return null;
}
// Untyped parameters make a function generic; each call instantiates it for
// the argument types it gets.
fn larger(a, b) { if a > b { a } else { b } }
// A struct is packed data in declaration order. Fields may carry defaults,
// which a literal can then leave out.
struct Point { x: f64, y: f64 }
struct Player { name: u8[..16], pos: Point, hp: i32 = 100, alive: bool = true }
fn main() {
// --- scalars -----------------------------------------------------------
// `let` binds a constant, `var` a variable. An integer literal adapts to
// the type it meets; on its own it is an i64, the default for counts and
// indices. The sized types are for data.
let answer = 42;
var count: i32 = 0;
let byte: u8 = 255;
let ratio = 0.75;
let single: f32 = 1.5;
let yes = 3 > 2;
count += 1;
count++;
print("answer=", answer, " count=", count, " byte=", byte, " ratio=", ratio,
" single=", single, " yes=", yes);
// Arithmetic runs at the operands' own width: u8 + u8 is an 8-bit add.
// A narrower type widens into a wider one implicitly; nothing narrows
// without a cast.
let wrapped: u8 = byte + 1; // unsigned math wraps by definition
let widened = byte + answer; // the u8 widens into the i64
let narrowed = answer as u8; // range-checked in debug, truncates in release
print(wrapped, " ", widened, " ", narrowed);
// `%` is Euclidean, so the result is never negative: `x % n` is always
// a valid index into an array of length n, whatever the sign of x.
print(-7 % 3, " ", 7 % -3);
// --- the array family --------------------------------------------------
// Every array is inline, packed element data; the kinds differ in how
// the size behaves. A fixed array has a constant size and no metadata.
var fixed: i32[4] = [1, 2, 3, 4];
fixed[0] = 10;
let zeros: f64[8] = [0.0; 8]; // fill form
// The grow-only array is the workhorse: a push is a pointer bump on the
// data stack, nothing is ever moved or reallocated, and references into
// it stay valid as it grows -- the promise a vector<T> cannot make, and
// the one the data-structure samples build on.
var grow: i64[>..] = [];
for i in 5 { grow.push(i * i); }
// A grow-shrink array also pops: the natural stack and queue.
var stack: i64[>..<] = [];
stack.push(1); stack.push(2); stack.push(3);
let top = stack.pop();
// A limited array has a fixed capacity, so it lives inline anywhere a
// fixed value can: in a struct, as an array element, in a local.
var small: u8[..8] = [];
small.push('h'); small.push('i');
// A variable array is sized at construction and frozen afterwards.
let frozen: i64[] = [7, 8, 9];
print(fixed, " ", zeros.len, " ", grow, " ", stack, " ", top, " ",
small, " ", small.len, "/", small.cap, " ", frozen);
// A string is a u8 array: no string type, no allocation. A literal is
// static data, a builder is a growable u8 array, and `format`, `str`
// and `print` render anything as text into one of those.
var s: u8[>..] = [];
s.append("hello");
s.push(' ');
format(s, "x", 1, " ", 2.5);
print(s, " (", s.len, " bytes)");
// --- slices ------------------------------------------------------------
// `a[x..y]` is a view of elements x up to y; `^k` counts from the end.
// A slice cannot dangle: the checker ties it to the array that owns the
// elements, and that array cannot shrink under it.
let middle = grow[1..4];
let last_two = grow[^2..];
print(middle, " ", last_two, " ", total(middle), " ", total(grow), " ", total(fixed_sum(fixed)));
// --- structs -----------------------------------------------------------
// A Player is one flat value: the name's bytes live inside it, not behind
// a pointer, so an array of players is one contiguous block.
var p = Player { name: "gus", pos: Point { 1.0, 2.0 } };
p.hp -= 30;
p.pos = p.pos + Point { 0.5, 0.5 }; // arithmetic is elementwise on same-typed structs
print(p);
print(p.name, " at ", p.pos.x, ",", p.pos.y, " hp ", p.hp);
// --- control flow as expressions ---------------------------------------
// `if` and `match` are expressions: no ternary, no temporary assigned in
// each branch.
let kind = if p.hp > 50 { "healthy" } else { "hurt" };
let level = match p.hp { 0 => "dead", 1..50 => "low", _ => "fine" };
print(kind, " ", level);
// `guard` states what the code after it relies on; its else must leave.
// `block` gives `break` a target, so a chain of checks can bail out with
// a value without a loop.
let verdict = block {
guard p.alive else { break "gone"; }
guard p.hp > 0 else { break "dead"; }
"standing"
};
print(verdict);
// --- loops -------------------------------------------------------------
var acc = 0;
for i in 3 { acc += i; } // 0, 1, 2
for i in 10..13 { acc += i; } // 10, 11, 12
for x in fixed { acc += x; } // copies of the elements
for &x in fixed { x *= 2; } // references: the mutation form
for x, i in frozen { acc += x * i; } // with the index
var n = 0;
while n < 3 { n++; }
loop { n--; if n == 0 { break; } }
print(acc, " ", fixed, " ", n);
// --- several results, optionals, references ----------------------------
// Two results come back together, as part of the calling convention:
// no tuple type, nothing packed and unpacked.
let q, ok = divide(7, 2);
let q2, ok2 = divide(1, 0);
print(q, " ", ok, " ", q2, " ", ok2);
// An optional must be narrowed before use: inside the `if` it is a plain
// reference to the element it found.
let big = first_at_least(grow, 5);
if big { print("first >= 5 is ", big); } else { print("none"); }
let none = first_at_least(grow, 100);
print(none == null);
// A reference reads and writes as its target; `.=` binds it (where `=`
// would copy the element) and retargets it, and `.==` asks whether two
// references name the same target.
var r .= grow[0];
r = 100;
r .= grow[1];
r += 1;
print(grow, " ", grow.index_of(r), " ", r .== grow[1]);
print(larger(3, 9), " ", larger(2.5, 1.5));
}
// A fixed i32 array widened into an i64 array, so `total` can take it: the
// slice type names the element type exactly, and i32 is not i64.
fn fixed_sum(xs: i32[4]) -> i64[4] {
return [xs[0], xs[1], xs[2], xs[3]];
}