Fields
A field is const unless marked var, and private or protected var decide who may read it and who may write it, independently of each other.
A field is a named piece of storage declared inside a type. Two modifiers decide what happens to it from outside the
type: var decides whether it can be written at all, and private/protected decide who may write it, or read it.
Example#
type Server {
host: String
var port: Int = 8080
protected var connections: Int = 0
private var log: List<String> = []
var fn record(message: String) {
log.append message
}
}
var server = Server host: "localhost"
server.port = 9090
server.record "started"
print "{server.host}:{server.port} has {server.connections} connections"Syntax#
<field> ::= [private] [var] <name>: <Type> [= <default>]
| protected var <name>: <Type> [= <default>]Rules#
A field is
constunless it is markedvar.host: Stringnever changes after construction, not even through avarbinding to theServer.var port: Intcan be written wherever a var path reaches it. The wordconstis what a field is without it, so writing it is refused - one spelling per meaning:Does not compiletype Server { const host: String } // error: `const` is what a field is without itA member is public unless it is marked
private.publicon a member says nothing and is refused the same way; it belongs on a top-level declaration, where the default is the other one. Reading a field cannot break an invariant, because values are never aliased and aconstfield never changes - so what needs protection is writing, and each of the four combinations answers a different question:Field Read from outside Write from outside host: Stringyes no, it is constvar port: Intyes yes protected var connections: Intyes no private var log: List<String>no no protected varis write-protected: everybody reads the field, and only the file that declares its type writes it. That is whatprotectedmeans here. TorbScript has no inheritance, so there is no subclass the word could open a field to. Outside the file,server.connectionsreads the count, and becauseconstis deep a copy taken out of it cannot be written back either. Inside the file - the body of the type, anextendof it there, and the functions of that file - it is an ordinaryvarfield. It is notprivate, so the constructor andcopystill take it.type Server { protected var connections: Int = 0 var fn accepted() { connections = connections + 1 } }Workspaceofstd/projectis such a type, and "outside" means outside the file it is declared in (rule 5):Does not compileuse Workspace from "std/project" var workspace = Workspace() workspace.memberPatterns = ["packages/*"] print workspace.memberPatterns // error: `memberPatterns` is write-protected: only the file that declares `Workspace` writes itprotectedgoes withvar, on a field, and nowhere else. A field withoutvaris written by nobody already, soprotectedin front of it has nothing to protect and is an error - one spelling per meaning. On a method, a case, astaticmember or a top-level declaration it is refused the same way, and a member has one visibility, soprivate protectedis an error too.Does not compiletype Server { protected connections: Int = 0 } // error: `protected` needs `var`: a const field is write-protected alreadyprivate(var) connections: Int, the spellingprotected varreplaces, is an error that names it, andtorb lint --fix --rule protected-fieldrewrites every one:Does not compiletype Server { private(var) connections: Int = 0 } // error: `private(var)` is written `protected var`privatereaches as far as the file that declares it. Aprivatemember is visible in the body of its type, in anextendof that type in the same file, and in the functions of that file - and nowhere else. Reading it from another file is an error, not a shorter view of it. One rule, and it is the same reach aprivatetop-level declaration has: what is private is what its file can see.Does not compileuse Path from "std/path" const path = Path.from "a/b" print path.componentValues // error: `componentValues` is private to `Path`Inside the file that declares it, nothing is hidden:
type Server { private var log: List<String> = [] } fn entries(server: Server): Int { server.log.length() } print entries(Server())A field may be named after a reserved word, and is then reached through a
..type: Stringdeclares a fieldtype; outside the type it isevent.typeandEvent(type: "click"), inside it isself.type, because a bare keyword is always the keyword. The derivedEncodeandDecodeuse the field's name as the key, so a JSON document with a"type"needs no renamed field. The same holds for a method and for the field of a case. See Lexical structure, rule 6.protectedis not reserved at all: it is a word only in front of a member, and a name everywhere else, bare or after a.(protected: Bool,self.protected).type Event { type: String var in: Int = 0 fn describe(): String { "{self.type} ({self.in})" } } print Event(type: "click", in: 2).describe()A field is a promise, not a first draft. It is a parameter of the generated constructor, a position in a pattern, a parameter of
copy, and a step of everyvarpath into the type - replacing it with a method later changes all four. What might one day be computed, cached or validated is a method from the start.
What this is not#
A field is not a shorthand for a getter and a setter. There is no way to attach logic to reading or writing a
field: a field is storage, and a method is what computes something. Wanting validation on write is a sign that the
member should have been a method from the start, not a reason to reach for protected var.
type Account {
protected var balance: Int = 0
var fn deposit(amount: Int) {
balance = balance + amount
}
}From another file there is no way in at all, and the deposit is the only door:
use Workspace from "std/project"
var workspace = Workspace()
workspace.memberPatterns = ["packages/*"]
print workspace.memberPatterns
// error: `memberPatterns` is write-protected: only the file that declares `Workspace` writes itprotected is not the protected of Java, C#, Kotlin, TypeScript or C++. There it opens a member to
subclasses. TorbScript has no inheritance, so the word means only this: write-protected.
Related#
- Declaring a type - fields alongside methods, constants and the generated constructor.
- Mutation and var paths - what has to be
var, from the binding down to the field. - Construction - what a field's default value is evaluated against.
- Bindings -
constandvaron a binding, the other half of the same rule.