I'm coming around to the idea that, like Zig and Rust do, we should make builtins (Str etc.) be explicitly imported - e.g. import roc.Str to bring Str into scope.
I remember back when we were upgrading to a new Elm version that had breaking changes in the String module, it was really useful to be able to do import FutureString as String to incrementally migrate the code base over, and then once we finally had everything working with FutureString, all we had to do to upgrade was bump the Elm version number and delete all the import FutureString as String imports
we don't support import FutureStr as Str in Roc because that would shadow Str, and I don't want to introduce shadowing of imports like that
also, static dispatch means that it's pretty easy to write an entire script that never needs to import these because it doesn't use type annotations and otherwise has no need to call things in a qualified way - e.g. https://gist.github.com/rtfeldman/81d906afed8142dba5eecee01df95e05 would not even be affected by this change
the main downsides as I see it would be:
Richard Feldman said:
- more boilerplate outside of scripts, but AI agents mean that boilerplate isn't the downside it once was
I had this exact thought process when I read this
Richard Feldman said:
- easier for people to use alternative sdlibs, which could theoretically fragment the ecosystem - I used to worry about this, but I've noticed it doesn't happen in Zig, and the closest thing I've observed in Rust is an async alternative to the I/O part of the stdlib, which doesn't apply to Roc. So I think the fact that this happens in e.g. Haskell and JS isn't necessarily cause for concern in Roc.
This thought process also came up as I was reading, but I couldn't figure out how people could actually implement a different stdlib since they are builtins.
The one hesitation I have is that since roc proper is so small what is there to actually be gained by a new implementation of builtins that a platform or module couldn't provide, although I like the FutureStr idea since that isn't technically external to the project
nandi said:
Richard Feldman said:
- easier for people to use alternative sdlibs, which could theoretically fragment the ecosystem - I used to worry about this, but I've noticed it doesn't happen in Zig, and the closest thing I've observed in Rust is an async alternative to the I/O part of the stdlib, which doesn't apply to Roc. So I think the fact that this happens in e.g. Haskell and JS isn't necessarily cause for concern in Roc.
This thought process also came up as I was reading, but I couldn't figure out how people could actually implement a different stdlib since they are builtins.
just different APIs that wrap the actual builtins, e.g. renaming Str.inspect to to_str etc.
Still kinda confused how something like import Str maps to wrapping to_str
The issue isn't just the time to type the extra boiler plate, it's also that every time you forget to put it in, you have an extra cycle where you have to read a compiler warning, fix it, and recompile. That would happen all the time to beginners and experts alike, and would be pointlessly frustrating.
The benefit of smoother migrations are much rarer. If it really is important, I think allowing shadowing default imports, while theoretically a messy compromise, would likely be less pain for people overall.
fair point!
I personally don't see much of a reason for this. Wouldn't mind it, but I don't think I have ever found this valuable in rust or zig.
We technically could allow import FutureStr as Str while still having Str automatically import otherwise. I think python does some of this with importing from __future__.
Given this is so rare, either is fine to me.
I like having them imported by default, it seems like a really niche use case to be able to rename them. Is there another way to deal with that, like Roc language editions or something.
editions generally work on the entire code base...the use case is being able to migrate individual files one at a time across the code base
hmmmmmm actually
if we do strings the way we do numbers, which is something I'd like to do anyway (e.g. you can make any type work with number literals by giving it a from_numeral method, and we can do the same for strings and from_quote) so that you can do things like have a function accept a Path or Url or Regex and yet you can give it a string literal because each of those types have from_quote methods
that would mean you could actually do the migration incrementally anyway, just give StrFuture a from_quote method, and then call StrFuture.whatever for direct calls
another language feature made unnecessary by static dispatch :joy:
Very cool
Would "my-str".some_fn() still always look in builtin Str for the static dispatch function?
yeah, just like how if (0.1 + 0.2 == 0.3) defaults to Dec
Last updated: Jun 16 2026 at 16:19 UTC