Lesson 01: Variables, Expressions & Control Flow
Welcome to the first lesson of the Tinsel Step-by-Step Tutorial. In this lesson, you will learn how Tinsel handles variables, mutability, scalar types, expression-oriented blocks, conditionals, and iterative loops.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Declare immutable (
let) and mutable (let mut) variables and perform simultaneous tuple swaps. - Work with integers, automatic arbitrary-precision
bigintpromotion, floats, characters, and interpolatedf"..."strings. - Use
{ ... }blocks andif/elsechains as value-producing expressions. - Write iterative loops using
while,break, andcontinue.
2. Variables & Immutability by Default
In algorithmic puzzles, accidental state mutation inside helper functions or loops is a common source of subtle bugs. Tinsel bindings are immutable by default.
Immutable Bindings (let)
A variable bound with let cannot be reassigned:
let target_year = 2025
let base_offset = 100
assert(target_year + base_offset == 2125)
// target_year = 2026 // Compile Error: cannot assign to immutable variable "target_year"
Mutable Bindings (let mut), Compound Assignment & Swaps
When you intend to update a variable over time, declare it with let mut. Mutable variables support standard reassignment (=), compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=), and simultaneous tuple reassignment:
let mut counter = 0
counter += 5
counter *= 4
assert(counter == 20)
// Simultaneous tuple reassignment (evaluates RHS before assigning LHS):
let mut a = 10
let mut b = 99
(a, b) = (b, a)
assert(a == 99 && b == 10)
Variable Shadowing
You can re-declare a variable with let in the same or an inner scope to transform values step by step without making the binding mutable:
let raw = " 42 "
let raw = raw.trim()
let raw = raw.to_int()
assert(raw * 2 == 84)
3. Scalar Types & String Interpolation
Integers (int), Bitwise Operations & Automatic bigint
Standard integers are signed 64-bit values. Integer division truncates toward zero, and literals can be written in decimal, hexadecimal (0x), octal (0o), or binary (0b) with optional _ separators:
let quotient = 17 / 5 // 3
let remainder = 17 % 5 // 2
let mask = 0xFF
let flags = 0b1011_0001
let shifted = (flags & 0x0F) << 4
assert(quotient == 3 && remainder == 2)
assert(mask == 255 && shifted == 16)
assert(255.hex(4) == "00ff" && 5.bin(4) == "0101")
Automatic bigint Promotion: Whenever an integer literal or an arithmetic operation (+, -, *) on 64-bit int values overflows the signed 64-bit range (> 2^{63}-1 or < -2^{63}), Tinsel automatically promotes the result to an arbitrary-precision bigint without wrapping:
let max_i64 = 9223372036854775807
let promoted = max_i64 + 1
assert(type_of(promoted) == "bigint")
assert(promoted - 1 == max_i64)
Floats (float), Characters (char) & Booleans (bool)
- Floats: 64-bit IEEE-754 floating-point numbers (
3.14,1e9,2.5e-4). - Characters: Single-quoted Unicode runes (
'A','\n','\u03b1'). Characters support direct offset arithmetic ('7' - '0' == 7,'a' + 3 == 'd'). - Booleans & Truthiness:
trueandfalsewith short-circuiting&&(AND) and||(OR). In conditions,nil,false,0,0.0,"",[], andNoneare falsy; all other values are truthy.
let digit_char = '8'
let digit_val = digit_char - '0'
let next_letter = 'a' + 2
assert(digit_val == 8 && next_letter == 'c')
String Interpolation (f"...")
Prefix a string literal with f to evaluate arbitrary Tinsel expressions inside {...} (escape literal braces with \{ and \}):
let part1 = 142
let part2 = 281
let summary = f"Part 1: {part1}, Part 2: {part2}, Sum: {part1 + part2}"
assert(summary == "Part 1: 142, Part 2: 281, Sum: 423")
4. Expression-Oriented Blocks & Conditionals
In Tinsel, { ... } blocks and if/else constructs are expressions. The value of a block is its final trailing expression:
let bonus = {
let base = 20
let multiplier = 3
base * multiplier + 5
}
assert(bonus == 65)
Because if ... else ... evaluates to a value, you can bind the result of a conditional directly to an immutable let variable:
let score = 85
let rank = if score >= 90 {
"Gold"
} else if score >= 75 {
"Silver"
} else {
"Bronze"
}
assert(rank == "Silver")
5. Iterative Control Flow: while, break & continue
Use while when iterating until a dynamic condition is met:
let mut i = 1
let mut total = 0
while i <= 10 {
if i == 5 {
i += 1
continue // Skip 5
}
if i > 8 {
break // Stop after 8
}
total += i
i += 1
}
// 1 + 2 + 3 + 4 + 6 + 7 + 8 = 31
assert(total == 31)
6. Common Pitfalls & Gotchas
Gotcha 1: Reassigning an immutable let binding
Function parameters, for loop variables, and plain let variables are immutable. If you need to update a counter or accumulator in a loop, always declare it with let mut.
Gotcha 2: Negative modulo remainders
Standard % in Tinsel (as in Go and C) preserves the sign of the dividend (-3 % 10 == -3). When wrapping circular dials or grid coordinates that may go negative, use ((a % m) + m) % m or math.euclid_mod(a, m) from std/math.
7. Hands-On Exercises
Exercise 1.1: Expression-Based FizzBuzz (Warmup)
Compute the FizzBuzz labels for 1 through 15 using an if/else expression and count how many numbers are divisible by 3 or 5.
Solution & Explanation
let mut n = 1
let mut hit_count = 0
while n <= 15 {
let label = if n % 15 == 0 {
"FizzBuzz"
} else if n % 3 == 0 {
"Fizz"
} else if n % 5 == 0 {
"Buzz"
} else {
to_string(n)
}
if label != to_string(n) {
hit_count += 1
}
n += 1
}
assert(hit_count == 7)
println("Fizz/Buzz hits in 1..15:", hit_count)
Exercise 1.2: Collatz Stopping Time (Practice)
Starting at N = 27, apply N \to N / 2 if even and N \to 3N + 1 if odd until N == 1. Count the total number of steps required.
Solution & Explanation
let mut n = 27
let mut steps = 0
while n != 1 {
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }
steps += 1
}
assert(steps == 111)
println("Steps for 27 to reach 1:", steps)
Exercise 1.3: Digital Root Calculator (Challenge)
The digital root of a non-negative integer is found by repeatedly summing its base-10 digits until a single digit (< 10) remains (e.g., 9875 \to 29 \to 11 \to 2). Compute the digital root of 9875.
Solution & Explanation
let mut n = 9875
while n >= 10 {
let mut digit_sum = 0
let mut temp = n
while temp > 0 {
digit_sum += temp % 10
temp /= 10
}
n = digit_sum
}
assert(n == 2)
println("Digital root of 9875:", n)
Next Steps
In Lesson 02: Collections, Slicing & Yield Comprehensions, you will learn how to manipulate arrays, tuples, and hash maps, and how to transform sequences with for ... in ... { yield } comprehensions and lazy .iter() streams.