4.3 Construction and constructors
Literal. T{ f1: e1, f2, ..base }:
- fields without a default value MUST be given, otherwise it is a compile error;
f2is shorthand forf2: f2;..basetakes the remaining fields frombase(moving or copying them);- a literal is allowed only if all fields of
Tare visible at the point of use andThas no@sealedattribute.
Positional literal. T(e1, e2, …) — fields in declaration order; allowed under the same conditions and if T has at most 4 fields without a default value.
Named constructors are declared in impl with the word init:
@sealed data Account {
id: AccountId
owner: UserId
balance: Money
invariant balance >= Money.zero()
}
impl Account {
init new(owner: UserId) -> Self =>
Self{ id: AccountId(cluster.ids.next()), owner, balance: Money.zero() }
init open(owner: UserId, deposit: Money) -> Self | TooSmall {
if deposit < MIN_DEPOSIT { fail TooSmall(deposit) }
Self{ ..Self.new(owner), balance: deposit }
}
}
let a = Account.new(me)
let b = Account.open(me, Money.rub(500))?init name(params) -> Self | E— an associated function with access to all fields; the return type MUST beSelfor a union containingSelf.@sealedforbids a literal outside theimplof the type; construction only throughinit.T.default()exists if all fields have default values, or with@derive(Default).- Values of refined types in a literal obey §3.6:
User{ name: "Ann" }— checked at compile time;User{ name: Name.from(s)? }— at run time. - Embedded fields (§4.7) are given flat (
Dog{ name: "Rex", breed }) or as a whole by the type name (Dog{ Animal: a, breed }).
Type call. T(args) is resolved as follows: if a positional literal of T is allowed at the point of the call — it is a literal; otherwise, if T has init new — the call T.new(args); otherwise — a compile error. For a distinct type T(x) is a constructor, for a sharded actor — an ActorRef reference.
Destruction. A value is destroyed when its owner's scope is left, in the reverse order of declaration. A user destructor is impl Drop for T { var fn drop() { … } }; Drop is implemented only explicitly.
Destructors are synchronous. The body of drop MUST have no effects and no suspension points; only non-blocking calls that release local OS resources, marked @nonblocking, are allowed (close a descriptor, free memory). There are no asynchronous destructors.
Asynchronous cleanup — an explicit method and defer:
@must_close
data Conn { sock: Socket; open: bool = true }
impl Conn {
move fn close() -> () | NetError net {
shield { sock.sendGoAway()? } // cancellation does not interrupt the shutdown
open = false
}
}
impl Drop for Conn {
var fn drop() {
if open { // close() was forgotten: a safety net without the network
sock.closeLocal()
log.warn("Conn dropped without close()")
}
}
}
fn talk(addr: Addr) -> () | NetError net {
let c = Conn.dial(addr)?
defer c.close()? // the error of close() is returned from the function
c.send(HELLO)?
}@must_closeon a type: if a value can leave its scope without a call of themovemethod marked as closing (closeby default, otherwise@must_close(by: name)), or without transfer of ownership, it is the compile errormust-close.defer e?inside a function with errors: the error ofeis returned from the function if the function does not already return another error; otherwise it is written to the log.- A synchronous
Dropremains a safety net for panic and cancellation: it releases local resources and writes a warning, but does not touch the network.
Related diagnostics
bad-init, missing-field, must-close, positional-literal, sealed-literal
See also
Source: docs/spec/oxl-standard.en.md