A library written in SCUA now keeps its own names. An enum declared
in a module is that module's type, two libraries can each declare a
record Item with a describe method, and a type
a module declares can be named through its import alias, so
let err: sqlite.Error = { code = 5, message = "abort" }
builds the library's own record. Records no longer have a fixed width, a
parameter typed T? can be left off, and an enum that
carries data has a JSON spelling. One change is breaking, the enum one,
so read the upgrade section before you install.
#Modules own their enums
Until now enum Outcome in one file and
enum Outcome in another were one type, and the second
declaration was refused. In 0.26.0 an enum or flags type belongs to the
module that declares it. Two libraries can each declare
Outcome, you can declare your own Kind beside
a library's, a value of one never matches the other's patterns, and the
compiler refuses to mix them. Values still print by their declared name,
so output does not change.
Methods work the same way. A method is looked up on the record it was
declared on, not on the record's name, so two modules'
record Item types keep their own describe.
#Naming a module's types
let err: sqlite.Error = { code = 5, message = "abort" }
checks the literal against the library's declaration and builds the
library's own runtime type: its methods dispatch on your instance, it is
sealed, and its where refinements hold. Until now a caller
either hand-wrote a near-miss table or the library had to ship a
constructor. Ask for a type the module does not declare and the message
lists the ones it does. A method a library declares on its record also
type-checks on a value the library returned, as in
lib.make("x").describe().
A record literal returned from a function declared to return that
record is a real record.
fn make(n: string) -> Player return { name = n } end
hands back a sealed Player with its defaults filled and its methods
callable from any file, where it used to hand back a plain table. A
module may also export a method under a different name:
return { query_frame = to_frame } makes
db.query_frame(sql) work.
#Modules in subdirectories
import lib/mid names a module by its path and binds its
last segment, or whatever you write after as. A module's
own imports look beside the module first, so lib/mid.scua
doing import helper finds lib/helper.scua
wherever the package is placed, before walking the search directories.
Names that used to collide silently now refuse, with the fix spelled
out: a module two search directories both provide, an import alias that
is also a top-level function or a built-in name, or a file of yours
named after a built-in module.
For hosts, scua_add_module_path is the C API twin of
--mod-path. A package's examples/ can import
the module in its parent directory under a host, which is the layout
every package already uses.
#Records of any width
The limit was 16 fields, the width at which a plain table switches to hash-map storage. A record is a fixed layout that never needed that switch, so the limit is gone. A binding surface with one field per bound function, 26 for a SQLite wrapper or hundreds for a larger library, is a record like any other. Plain tables are unchanged.
A record literal may also leave out defaulted fields wherever a
record is expected: as an argument to a parameter typed
Player, as the value returned from a function declared
-> Player, or assigned to a Player
variable. When the literal is wrong the error names the field,
missing field 'hp' of type number, rather than describing the
whole table.
#Optional parameters and
checked ? calls
A parameter typed T? may be left off.
fn open(path: string, opts: Options?) can be called as
open(path), and opts arrives as
nil, which is what the type already allowed. Everything
after an omittable parameter is omittable too, as with defaults.
A call written f(x)? is now checked like
f(x). The ? operator used to skip every check
on the expression inside it, so a wrong argument count could pass
unnoticed until a plain call failed. Code that relied on that gap with a
parameter that is neither defaulted nor T? is refused at
the call now.
#Tagged enums in JSON
A variant that carries data encodes as
{ "kind": "Text", "value": "hi" }, with value
an array for several fields and omitted for none. A decoded object of
that shape becomes the variant again wherever a record field, or a
{ Item } array field, is declared with the enum, so
match works on what a JSON caller sent. A wrong
kind or a wrongly sized value is refused at
the boundary, by field. scua schema publishes such a field
as a oneOf over the variants, and it now defines every
record it refers to, at any depth, so a consumer resolving a reference
finds the record. Ok(…) and Error(…) encode
the same way.
#Bytes values have methods
Every bytes function is a method on a bytes value:
b.to_array(), b.to_string(),
b.slice(a, b) and the rest, with no
import bytes needed.
#For source builds with the FFI
The dynamic FFI, still opt-in for source builds made with
-Dffi=true, holds up to 256 declared native types per
partition, twice what it did, and a binding that reaches the limit is
told so by name, with the way out. Native libraries and
FFI has the setup and the limits.
#Upgrading
Check two things before you install 0.26.0.
Delete any verbatim copy of a library's enum you kept to match its values. It is a separate type now, and the compiler hints to say so.
Drain or re-save anything persisted that holds values of a
module-declared enum. Such saves and hibernated sessions are refused on
load with a schema mismatch. Saves holding only your own file's enums
load exactly as before, and durable and cluster data
written by 0.25.0 still loads when the enum name is unambiguous in the
reading program.
The full changelog has the detail.