Values and copying
In Tessel, every value behaves like a number. When you assign it to another variable, pass it to a function, or put it in a list, the receiver gets its own copy. Changing one copy never changes another. This is true for structs, enums, strings, lists and dictionaries alike.
struct Point { x: Int y: Int}
fn main() { var a = Point(x: 1, y: 2) var b = a // b is a copy of a b.x = 99 print("{a.x} {b.x}")}1 99If you have used Swift, this is how Swift structs and arrays behave. If you
come from JavaScript, Python, Java or C#, it’s different: in those languages
b = a makes two names for the same object, so changing b.x would change
a.x too. In Tessel it never does.
Why values?
Section titled “Why values?”With values, a piece of data can only change through the variable that holds it. No other part of the program can change it behind your back. That makes programs easier to follow, and it rules out a whole class of bugs where two parts of a program accidentally share and modify the same data.
It also keeps memory management simple. Tessel has no garbage collector and no pauses to clean up memory: Tessel counts how many places use each piece of storage (reference counting), and frees it as soon as nothing uses it any more.
Copies are cheap
Section titled “Copies are cheap”Copying a big list every time you pass it somewhere would be slow, so Tessel doesn’t actually do that. Strings, lists and dictionaries are copy-on-write: a copy at first shares the same storage as the original, and the storage is only duplicated at the moment one of them is changed. Passing a large list to a function that only reads it copies nothing.
You never see this sharing in your program. It only affects speed, not behavior.
Changing values in place
Section titled “Changing values in place”Because a copy is independent, changing it doesn’t change the original. To change a value that lives inside another value, change it through the original’s path, all in one expression:
struct Todo { title: String done: Bool = false tags: [String] = []}
fn main() { var todos = [Todo(title: "Write"), Todo(title: "Test")]
// Taking an item out gives you a copy: var first = todos[0] first.done = true print(todos[0].done)
// Changing it through the list changes the list: todos[0].done = true todos[1].tags.append("urgent") print("{todos[0].done} {todos[1].tags.count}")}falsetrue 1Any path made of variables, fields and indexes works:
todos[1].tags[0] = "later", game.players[i].score += 1, and so on. The
path has to start at something that can change: a var, a state, a
bind parameter, or a loop variable over one of those. Through a let,
nothing inside can be changed either, not even a field of an item of a list.
Loop variables
Section titled “Loop variables”When you loop over a list you can change (for example, a var), the loop
variable is the item in the list, not a copy. Changing it changes the list:
struct Todo { title: String done: Bool = false}
fn main() { var todos = [Todo(title: "Write"), Todo(title: "Test")] for todo in todos { todo.done = true } print(todos[1].done)}trueWhen the list can’t change (a let, a parameter, or the result of a call),
the loop variable is a constant.
Snapshots
Section titled “Snapshots”Since a copy never changes, you can keep one as a snapshot of the past:
var scores = [10, 20]let before = scoresscores.append(30)scores[0] = 0print("before: {before.count} items, first {before[0]}")print("now: {scores.count} items, first {scores[0]}")before: 2 items, first 10now: 3 items, first 0Functions and copies
Section titled “Functions and copies”A function receives copies of its arguments, and its parameters can’t be changed. A function that “changes” a value takes it and returns a new one:
struct Account { owner: String balance: Int = 0}
fn deposit(account: Account, amount: Int) -> Account { var updated = account updated.balance += amount updated}
fn main() { var account = Account(owner: "Ada") account = deposit(account: account, amount: 50) print(account.balance)}50Often a method is the more natural way to change a value. A method can change the value it is called on, as long as that value is in a variable. See Structs and enums:
struct Account { owner: String balance: Int = 0
fn deposit(amount: Int) { balance += amount }}
fn main() { var account = Account(owner: "Ada") account.deposit(amount: 50) print(account.balance)}50The exception: closures
Section titled “The exception: closures”A closure uses the variables it
captures directly, not copies of them. Copies of a closure also share those
variables. This is what lets a closure update a counter or a view’s state.
Sharing on purpose in views
Section titled “Sharing on purpose in views”Sometimes two parts of an app really should work on the same data: a
TextField needs to change the text stored in its parent view, for example.
For that, views have bind parameters, which receive a place to read and
write rather than a copy. See State and bindings.