Brendan Hansknecht said:
I assume that would be a runtime check in dev and compile time check in release?
In many places within community chat, I've seen what, afaict, is assumption of a Rust-style tradeoff between dev and release compilations, specifically dev builds minimizing time to first app instruction, while prod provides extra compile-time safety and performance optimizations.
Is that truly a binary tradeoff though? I see extra compiler analysis checks as shortening the dev-test loop (since those extra compile checks are, at least, a kind of "test"); any correctness test early in the dev loop is something I ultimately view as making a better use of developer time compared to unexpectedly finding out about those correctness issues later on ("okay, dev build succeeds, unit tests run, even dev build deployed to the staging environment passes unit tests, so I'm ready to release, right?").
Please forgive the probably inaccurate use of terminology, but can we have a modular, incremental build cache, such that a dev build can quickly produce a binary for app-level testing, yet a separate process can continue (or resume) compilation past the "dev" stage towards release quality (at least any further correctness analysis)?
If I change nothing about the source code but start another "build" after the background/extended compilation has been built and cached, the compiler can deliver those results to me for free, in the form of new errors. Likewise, if I use a module that has a cached release build, at least those non-inlineable but otherwise optimized code-paths from the cache should be able to make it into my dev build.
If byte-for-byte reproducible builds are desirable, that can be a guarantee of release builds, but not a guarantee of dev builds, since dev builds would require a minimum level of processing, but could opportunistically make use of more than that minimum level if available, or if it fits within a time budget ("if the non-linking part of the build takes less than 500ms, then use up to 500ms to see if anything more valuable comes out of it").
prod provides extra compile-time safety
It's the opposite in rust AFAIK. Dev builds provide extra safety guarantees. Like error on numeric overflow.
unoptimized and optimized builds both provide the same safety guarantees. They will both panic in the same situations ( i.e numeric overflow ). In current roc, there should be no correctness difference between unoptimized and optimized builds.
With optimized builds, we run morphic which lets us know which pieces of data are always unique. This can be used for removing some refcounting instructions and things of that nature. This does not effect correctness but does effect performance. We don't run it on dev builds because we want dev builds to be blazing fast.
If we had some sort of expect unique and it was a runtime check, I think it would mostly defeat the purpose of it. If it is a runtime check, it is no different then the code that is currently generated except it will panic when you get it wrong instead of copying the data. The goal of this uniqueness check is to guarantee removal of those branches and the copying in general. Also removal of any related refcount updates.
This should almost certainly be a compile time error in general. The question is how it would be implemented such that we can still compile fast and make it a compile time error. If it depends on morphic, it would likely be "too costly" for unoptimized builds. That is why I made the comment about unoptimized vs optimized builds
specifically dev builds minimizing time to first app instruction
Just a general note, one of the goals is for Roc to essentially be usable as a scripting language. Compile fast, run with ok speed, compile code with errors and hope for the best. Also, another part of fast compilation is around a smooth editor experience where we may want to attempt to incrementally compile and hot code reload an app at 60 fps as someone is using a slider in the editor.
Is that truly a binary tradeoff though? I see extra compiler analysis checks as shortening the dev-test loop
It definitely isn't binary though deciding what and how to expose the configuration is an open question. For example, roc check will block programs that will build with roc build. Something like this may help give each developer the experience they care most about.
Please forgive the probably inaccurate use of terminology, but can we have a modular, incremental build cache, such that a dev build can quickly produce a binary for app-level testing, yet a separate process can continue (or resume) compilation past the "dev" stage towards release quality (at least any further correctness analysis)?
This is an interesting idea. Might be complex to implement, but definitely worth thinking more about. Rather than just incremental unoptimized or optimized builds, have incremental builds that upgrade from unoptimized to optimized, sharing work (parsing and canonicalization at least).
so expect runs in dev builds as well as during tests, so if we had an "expect unique" check which did a runtime check, you could verify it via roc test - which isn't compile time, but it is build time
and doing the runtime verification would also catch situations where the optimization actually does trigger at runtime only (because a refcount is 2+ at some point but drops to 1 before hitting that code path, so it's unique at the point of the check at runtime in practice) which would mean having the test rely on morphic could lead to false negatives
on the other hand, maybe there's some case where we'd want to be able to assert "this has been verified to be unique in all possible code paths, not just the ones I've covered in tests" - but I'm not sure how important that distinction would be in practice!
@Richard Feldman unique for all possible code paths could be valuable to a module developer, even if such exhaustiveness is more expensive to prove and warrants a separate build flag or subcommand. I agree that for an app build, there really isn't a meaningful distinction between "possible code paths" and "reachable code paths."
Such a module level proof would imply a subtlety to the kind and scope of such possible uniqueness checks though, ordered least to most strict:
#2, but this function may yield fewer of the same references, for example, a list-wise min of strings.If such assertions are valuable (they might not be), it implies the need for a distinction between "point sampled" (context-unaware) assertions and span sampled ("from here to there") or directional assertions (coming in from up-stack vs from here all the way down-stack)
- Neither this function, nor its callees, heap allocate at all, if given a unique reference.
This is kinda what I am interested in, but is much more strict. I just want the guarantee that a specific variable will never be cloned in a specific function or its callees. It might still heap allocate, because a value might get appended to it.
I guess I would also like to add. I think it would be good to also guarantee that the function is always called with unique variables. If the value would need to be cloned, I would want that to to be explicit and in the caller.
Last updated: Jun 16 2026 at 16:19 UTC