I noticed today that duplicate record fields are allowed and only trigger a warning.
module [foo]
foo =
{ bar: "hello", bar: "goodbye" }
── DUPLICATE FIELD NAME in Module.roc ──────────────────────────────────────────
This record defines the .bar field twice!
4│ { bar: "hello", bar: "goodbye" }
^^^^^^^^^^^^ ^^^^^^^^^^^^^^
In the rest of the program, I will only use the latter definition:
4│ { bar: "hello", bar: "goodbye" }
^^^^^^^^^^^^^^
For clarity, remove the previous .bar definitions from this record.
────────────────────────────────────────────────────────────────────────────────
0 errors and 1 warning found in 39 ms.
Is there a reason to support this? I can't think of a scenario where I would not want the compiler to give me an error for doing this.
in general, the philosophy behind warnings vs errors is:
put another way, warnings are "will not affect end users in any way, but you definitely should clean this up" whereas errors may actually affect end users
The concern I have is that duplicate record fields as a warning makes the developer experience worse. I ran into this because I introduced a bug by accidentally typing bindings: [{name: “:field1”, value: String “foo”, name: “field2”, value: String “bar”} with sqlite instead of two records. I didn’t see the warning because my language server wasn’t working properly and there was another warning that prevented me from seeing the duplicate field warning in the terminal output.
Maybe not having the language server working is enough of an edge case that it isn’t that important, but I was very surprised when I found the cause of the bug and saw that my program was still compiling with such an obvious error.
With things like unused fields I definitely only want a warning and not an error, because I may have just not used the field yet. But duplicate record fields are nearly always a mistake, and likely to cause bugs, so I would rather hear about it loudly and quickly.
that's fair, but this is a pretty specific bug - I think if we adopted a design principle of "but what if there's a bug in this particular part of the implementation?" I don't think it would go well overall :sweat_smile:
I'm actually in the "this should be an error" camp.
I feel like another way to view warnings and errors.
Is:
If there is no reason why a person should ever do a thing that could cause a problem it should probably be an error.
Like In my view, I can't think of any reason I would ever want duplicate record fields in my code, and I can imagine them causing confusing bugs.
Just my 2c
You know, you have a point. If I wrote a record with duplicate fields, it wouldn't be on purpose. If I was running roc dev myfile.roc and it didn't crash at that point, I'd have let's say a 50% chance to get the value I wanted for the duplicate field. As much as we want roc dev to be able to bulldoze forward as much as possible, this seems like one of those cases where we should avoid "silent failure".
I would vote for a runtime error on loading the field. Just note that it is ambiguous which version of the field to load.... But that is just a gut feeling and not a well thought out decision
I would much prefer have a compile time error. There’s no reason i want to be able to run my program with duplicate fields at all so why delay the error to runtime?
+1 on compile time error
For reference, python and js both allow duplicate fields and just uses the second. JS throws an error on duplicate fields.
Noting this cause without types, roc is attempting to feel similarly flexible as these languages.
python allows duplicate fields and just uses the second
:melting_face:
Sorry, js also does the same as python in the browser. The first online repl I used just avoided that.
So if we are going for a flexibility similar to js and python, a warning sounds right.
Given that you can add fields to objects in js and python I think it would be surprising if they disallowed duplicates. But that is not relevant for roc so I see no benefit from allowing it
Sure, but this is the same field twice in a single record. That is the same as record update syntax essentially
It has equivalent value to that of js or python doing the same
I think the usefulness of it in javascript or python is so that you can run the spread operator and overwrite some keys ({**kwargs, “foo”:1}). Roc solves that a different way. I can’t think of a good reason you’d want to write two same keys in the same literal record, but that’s just me
I don't think I'd do this intentionally even if I didn't know Roc's syntax, so I'd always want this to crash
@Brendan Hansknecht Do you see value in allowing duplicates in Roc outside of being consistent with what JS and Python do? I don't think we should try to mirror JS or Python unless the feature will be intrinsically beneficially for Roc
For me, if I accidentally duplicate a record field while programming, I see no reason to crash or stop compilation
I can still keep tinkering and tests and execution should just work
That is part of the awesome flexibility of roc just running code even if it isn't fully correctly formed
So a warning is enough and it will get fixed up later.
I use js and python as references cause they enable a level of tinkering that is essentially not possible in static languages. Roc is trying to bridge that gap by running anything while people are tinkering and just adding in warnings + runtime errors.
This is uber important for the feel of developing in roc. Every single item (even small ones like this) that become compile time errors instead of runtime errors hurts that feel. So I think something has to have a crazy important reason to be an error instead of a warning. Over time, I think less and less things should be compile time errors.
Well, why does something like division by zero cause a crash? I'd say that anything that leads to incorrect logic should still allow running code, but cause a crash when it's run to prevent snowballing out of control
(edit: this got sent before it was finished, will repost below)
Brendan Hansknecht said:
For me, if I accidentally duplicate a record field while programming, I see no reason to crash or stop compilation
It seems like it's very likely to be a mistake by the developer that may alter the execution. You could have a bunch of warnings printing while tinkering and miss this one.
When developing code and running a script, you'll be running it multiple times. It's faster to fix a bug by discovering it ASAP and fixing it a single time than to have your code run anyway, get the wrong answer, and scratch your head a lot
The impact on tinkering seems small since you can already run your code with errors using roc run
I think we disabled that for now because the experience was not good enough with vague error messages
I also really like how fluid and flexible Roc is for tinkering, but I would always like to immediately fix duplicate record fields because it's such an easy fix to an obvious mistake that is likely to cause bugs
I think we have to keep in mind that Roc isn't going to match the scripting features of a JS or Python. For example,
x = ""
if x then ...
just isn't going to compile, and crash immediately once you hit the if branch. Now it's a benefit that you can partially execute the program, but changing the semantics to fully execute it would be in stark contrast to how the language works today.
Agreed with what Isaac just said, idk why you would ever intentionally duplicate record fields
I think we disabled that for now because the experience was not good enough with vague error messages
roc run still works with my Roc from 2024-12-21. But I haven't used it in practice without it crashing
idk why you would ever intentionally duplicate record fields
Yeah, would never be intentional
It seems like it's very likely to be a mistake by the developer that may alter the execution
Correct
You could have a bunch of warnings printing while tinkering and miss this one.
Also, correct.
That is the behaviour I want when tinkering. If my tests/execution examples don't catch it, I don't care about it when tinkering. Will care later in a cleanup phase. If my tests do hit it, I'll fix it sooner.
I think we have to keep in mind that Roc isn't going to match the scripting features of a JS or Python.
Sure, but here we easily can match and I don't think it really costs anything to do so.
I think it would help to consider the UX differences we expect here based on https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/revised.20.60roc.60.20CLI.20subcommands which removes roc dev
in that world:
roc check reports all warnings and all errors, as does the language server. Warnings might look a bit different (e.g. be a different color) but they still cause a nonzero exit code. So here we're talking about a cosmetic difference only.roc (formerly roc run) runs regardless of whether there are errors or warningsso it seems like in that context, the discussion is about whether it should be rendered visually as an error or as a warning, yeah?
runs regardless of whether there are errors or warnings
Definitely missed that. I thought error meant it would not run.
If it is just about visuals then I think an error makes more sense. I just think it should still run.
in that design, there is no "errors block you and warnings don't block you" anymore.
the philosophy becomes "we always tell you about errors and warnings in your editor, and if you would like to run at any time even though you see errors and/or warnings, it will run as requested!"
Richard Feldman said:
so it seems like in that context, the discussion is about whether it should be rendered visually as an error or as a warning, yeah?
I have been thinking of this discussion as a choice between:
ah, I see
and have been pushing for the latter because I think it would save me more headache
:thinking: what if we treated it as a failed inline expect? so it gets reported at runtime but doesn't halt the program
If that's a general approach we took, I'd be interested!
it fits with the theme of "inform, don't block"
I thought a failed inline expect halts the program
nope, it's the most foundational design goal of inline expects that they don't halt the program :big_smile:
why not try the most restrictive thing and see if anyone feels the pain? it's always easier to loosen restrictions than try to add new ones later.
Oh, interesting. Also, I guess the behaviour will be up to the platform if they just want to log the failure or they want to stop execution.
Sam Mohr said:
Richard Feldman said:
so it seems like in that context, the discussion is about whether it should be rendered visually as an error or as a warning, yeah?
I have been thinking of this discussion as a choice between:
- When we see a duplicate field, we pick one of them and keep running
- When we see a duplicate field, we crash at runtime
I've been talking about something different :sweat_smile:
Namely, whether duplicate record fields should be a compile time warning, or a compile time error that prevents you from running the program by default. I'm less interested in the runtime behavior if you choose to run the program anyway.
This was under the assumption that we would still have errors that would normally block you from running the code.
I'm not sure of many languages that take that approach? Can you give an example?
Isaac Van Doren said:
Namely, whether duplicate record fields should be a compile time warning, or a compile time error that prevents you from running the program by default. I'm less interested in the runtime behavior if you choose to run the program anyway.
This was under the assumption that we would still have errors that would normally block you from running the code.
yeah I think we should eliminate "compiler error that blocks you from running the code" as a concept
that was the original idea, but it was too annoying before we had a language server, so we added roc dev - I think now that we have the editor assistance we should go back to the original design!
Ayaz Hafiz said:
why not try the most restrictive thing and see if anyone feels the pain? it's always easier to loosen restrictions than try to add new ones later.
Personally, I think that for running with errors and warnings we should always start with as few restrictions as possible. Make roc feel as dynamic as possible for those cases. Only restrict if it turns out to be a headache and error prone in practice. For any sort of serious or checked in code, all errors and warning will be dealt with before checking in the code.
If you never want to run with errors/warnings, you can use roc check first.
I strongly suspect we would never be having this discussion if there hadn't happened to be a language server bug that prevented the "inform" part of "inform, don't block" from working as intended :big_smile:
My experience using roc run has been completely negative so far (because it always crashes for me), so getting rid of blocking errors worries me. But maybe if we can make sure that the vast majority of people are using the language server and that it is reliable, that won't be an issue
One person's opinion, but I don't understand the hype for "run something that will fail or have obviously undesired behavior". That's the type of thing I come to a strongly typed, compiled language to prevent.
Yeah I basically always want to fix all errors before running my program
And to @Isaac Van Doren 's point, it always ends up crashing, and the crash almost never has helpful information. So much so, I pretty much always just run roc build && ./main instead of roc main.roc
serious question: when you see errors listed in your editor and decide to run the program anyway, what is the goal of running the program? :thinking:
I basically never do run the program when there are errors. But the times I have tried it out, it hasn't worked
I don't really understand the motivation behind "I am aware that there are compile errors, but I want to run the command that runs the program and see it stop and tell me again about the errors I already knew about"
yeah I understand that the "run even though there are errors" experience is too buggy to be useful right now :big_smile:
I don't always use an LSP personally, especially when learning a new language
but we have a separate problem of the CLI having too many options and being confusing
Yeah, I think it might be ok if we always run check and output the reports even if I just use roc main.roc
And one of those reports is an error report for duplicate record fields :smile:
Most of the time, I work in one of two flows. Either:
roc test my-file.roc (or roc my-app.roc for some executables)roc check my-file.roc && roc test my-file.roc.It depends on the app if I want to just always run or if I want types to guide me.
So I regularly run roc code that has errors or warnings.
that's fair, but we've also always had the design goal of designing the language with the assumption that people are going to have editor assistance, and the friction of getting a Roc extension going is now low in all the popular editors
Anthony Bullard said:
Yeah, I think it might be ok if we always run check and output the reports even if I just use
roc main.roc
oh yeah we should always report them!
maybe a better way to articulate the design philosophy is "always inform, never block"
I think if there's a large discrepancy between where the goal of the language will be and where it is right now in terms of stability, tooling etc we should prefer to optimize for the short term and give a good experience there
like if the compiler knows there's a problem, it should always tell you about it
I don't think there's a large discrepancy though :big_smile:
like fundamentally the editor tooling already tells you whether or not there are errors (I guess modulo the particular bug that motivated this discussion, but I'm guessing that's not a huge challenge to fix?)
Brendan Hansknecht said:
Most of the time, I work in one of two flows. Either:
- On every save, automatically
roc test my-file.roc(orroc my-app.rocfor some executables)- On every single save, automatically
roc check my-file.roc && roc test my-file.roc.It depends on the app if I want to just always run or if I want types to guide me.
But neither of those flows run the code if there are compile errors right? :thinking:
Unless the change to make roc equivalent to roc run is already in?
Also why do you need to run check before test if test already reports all of the same errors that check does?
so if the design is "your editor should tell you in realtime whether running the program is going to potentially result in crashes, so you already have that info before deciding whether to run the program which means you're running it because you actually want it to run" then I don't think having a better-working "run anyway" implementation changes anything there
btw I think the same goes for testing: roc test should always run the tests even if there are errors, and it should also always report the compilation errors along with the test failures
(again because if you're running the tests, you would have done that having already known from the editor about the errors!)
I think the unfortunate reality is the editor tool still crashes a lot because of existing bugs in the compile
during type checking?
But neither of those flows run the code if there are compile errors right?
I thought roc test still ran on errors, but I guess not. I would prefer for it to do so. Also, roc run is what I meant above for clis.
Also why do you need to run check before test if test already reports all of the same errors that check does?
Sadly, anything that actually builds the executable can break in deep compiler bugs. So I always run check if I actually want good type checking with info.
yes or some weird state parsing cant recover. I definitely have this happen a lot and have to restart the tool. Agreed with the long term goal
Is anyone actually running roc run without a roc check before right now?
and if so, what frequency of the time?
I am actually looking through my terminal history and it is a lot rarer than I remember.
I mostly use roc check, roc test, and plain roc which is roc dev
I'm not sure what percentage of the time I hit actual errors (that could compile) vs just warnings. I definitely run tons of roc code with warnings, but maybe I run it less often with errors than I realized.
:thinking: I wonder if the solution to the CLI problem is:
roc dev but just have roc work that waythat way we solve the confusing CLI problem and have people not be exposed to the super buggy "run anyway" experience unless they opt in with a special env var
+1
we shouldn't let good be the enemy of perfect. we know how to run programs with errors but we're far from having that and a bunch of other stuff work well, i think it's better to make the common use cases work consistently first
That sounds great to me! In that world roc run would be completely replaced by the env var for now also right?
which env var?
The hypothetical env var Richard suggested to enable running with errors present
Could also be a flag
I think just an "--ignore-errors" flag would be sufficient
That has more visibility than an env var that can be lost after it's been exported
Still leaves the balance of what should be warnings vs errors. For example, duplicate fields with identical values are probably just a copy paste bug and harmless while differing fields is a more complex decision.
Also, the advantage of an env is that someone can turn it on in general if they prefer that behaviour.
It is, but you can also leave it on accidentally and not understand why you are getting strange behavior
That's why I prefer env for things like paths and stuff which will stay the same
But as always, not a hill I'd die on.
Yeah, isn't as visible as env for logging or for level of details in a backtrace (which are both quite common use cases for env)...
Hmm, actually this would be pretty noticable if left on. You would see errors, but roc test and roc ... will still run anyway. So you would still see a visual due to all for the error print outs.
I think there are still problems that we can't recover from? How would we run this code?
x = if "abc" then 123 else 456
y = Str.from_utf8(Tag)
Stdout.line!("${Inspect.to_str(x)}, ${Inspect.to_str(y)}")
That's the long-term distinction between warnings and errors in my mind
Stuff we can bulldoze through, and stuff we can't
i think that just needs to crash immediately
I agree, I think this is an "error" since we can't bulldoze through
i think a good heuristic is anywhere there is a type error, crash immediately until the next jump
so like that example just becomes crash ".." for everything
x = if True then 1 == "" else 2
x + 1
just needs to be
x = if True then crash ".." else 2
x + 1
the latter won't work right now but it can be made to work
That's my understanding
Yeah, so in Sam's example, it would just be x = crash ...
Last updated: Jun 16 2026 at 16:19 UTC