Stream: announcements

Topic: Breaking change with field defaults


view this post on Zulip Aurélien Geron (Aug 31 2026 at 23:23):

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.

view this post on Zulip Aurélien Geron (Aug 31 2026 at 23:33):

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?

view this post on Zulip Richard Feldman (Aug 31 2026 at 23:50):

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

view this post on Zulip Aurélien Geron (Sep 01 2026 at 00:16):

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?

view this post on Zulip Richard Feldman (Sep 01 2026 at 13:21):

kinda, but also doing it correctly would require executing userspace Roc code during type checking :sweat_smile:

view this post on Zulip Richard Feldman (Sep 01 2026 at 13:22):

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

view this post on Zulip Richard Feldman (Sep 01 2026 at 13:25):

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

view this post on Zulip Richard Feldman (Sep 01 2026 at 13:28):

that said, you should be able to get the function calling ergonomics you want anyway from a nominal type

view this post on Zulip Richard Feldman (Sep 01 2026 at 13:30):

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

view this post on Zulip Aurélien Geron (Sep 01 2026 at 18:36):

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