It looks like there's a breaking change with field defaults: they're only allowed on the fields of a nominal record type now:
Field defaults (??) are only allowed on the fields of a nominal record type declaration's backing record, not in structural record types (type aliases, inline annotations, or nested records).
I noticed a bug: I can't assign {} to a record with 100% optional fields. I opened issue #11024.
I used optional fields for function arguments with default values, for example:
KillerSudokuHelper :: {}.{
Combination : List(U8)
combinations : { sum : U8, size : U8, exclude : List(U8) ?? [] } -> List(Combination)
combinations = |{ sum, size, exclude }| {
...
}
}
But now it looks like this:
KillerSudokuHelper :: {}.{
Combination : List(U8)
ArgsWithDefaultExclude := { sum : U8, size : U8, exclude : List(U8) ?? [] }
combinations : ArgsWithDefaultExclude -> List(Combination)
combinations = |{ sum, size, exclude }| {
...
}
}
It's not quite as nice. Could we automatically define an anonymous nominal type under the hood for this use case, so the first code would work again?
Aurélien Geron said:
It looks like there's a breaking change with field defaults: they're only allowed on the fields of a nominal record type now:
Field defaults (??) are only allowed on the fields of a nominal record type declaration's backing record, not in structural record types (type aliases, inline annotations, or nested records).
oh that was always the design - the error message might just be new
The first code example worked until yesterday, though, and it was convenient for function args. Curious to know why it has to be nominal? Is it compilation speed?
kinda, but also doing it correctly would require executing userspace Roc code during type checking :sweat_smile:
because structural types are defined to be equivalent if their types are equal, and the default value would be part of the type - e.g. a structural record with ?? 1 and ?? 2 would be different types even if everything else was the same
but you're allowed to put arbitrary expressions after the ?? - e.g. ?? blah() - so the only way to know if the types are structurally equal would be to execute all that during type checking, which I think is a bad idea
that said, you should be able to get the function calling ergonomics you want anyway from a nominal type
because structural records will unify with (non-opaque) nominal types, so for example if you do this...
Config := {
name : Str,
things ?? 0,
}
my_fn : Config -> Blah
...then you should be able to do:
my_fn({ name: "" })
and it should work
Got it, thanks. Note: it looks like this is causing a segfault on Linux x86_64 (but it works on MacOS apple silicon). I'm AFK so can't submit an issue right now.
Last updated: Sep 03 2026 at 15:16 UTC