Picking up basic-cli #386 next — read a UTF-8 file, substitute, write it back. It's pure Roc on top of Path.read_utf8! / Str.replace_each / Path.write_utf8!, so no host or glue changes.
Three things I'd rather settle before writing it:
1. Where it lives. The issue asks for both File.replace_utf8 and Path.replace_utf8, but File is now only buffered readers and all whole-file operations moved to Path. So I'd add just Path.replace_utf8! and leave File alone — matching @Luke Boswell's triage note. Worth retitling the issue too?
2. Scope. Start with the replace-all form only, mirroring Str.replace_each:
replace_utf8! : Path, Str, Str => Try({}, [PathErr(IOErr), ..])
Or add replace_first! / replace_last! at the same time, since the Str counterparts now exist? My preference is just the "each" form — that's what the issue asks for, and the others are easy to add later if anyone wants them.
3. Atomicity — the one I'd most like a second opinion on. Read-modify-write is not atomic: if the process dies mid-write, the file is left truncated. Two options:
Path.write_utf8! already has exactly that property, so replace_utf8! would behave like its parts.Path.rename! over the original. Atomic on the same filesystem, but it pulls in temp-file naming, cleanup on failure, cross-device fallback, and preserving permissions/ownership.I lean toward the first — the second is a meaningful behavior change that probably deserves its own issue covering write_utf8! too, rather than being smuggled in here. But if you'd rather replace_utf8! be the safe one by default, I'm happy to do that instead.
Minor: Str.replace_each names its arguments source, delimiter, replacement. delimiter reads a bit odd for file contents — any objection to needle/pattern, or should the platform stay consistent with the builtin?
So I'd add just
Path.replace_utf8!and leaveFilealone
This sounds good @Dzmitry Misiuk, I have retitled the issue.
My preference is just the "each" form
Yeah, go with that.
I lean toward the first
:+1:
any objection to
needle/pattern
I like pattern.
Thanks for settling all four @Anton — opened as #461. Path.replace_utf8! only, "each" form, pattern, and kept non-atomic with a doc note. Pure Roc over read_utf8! / Str.replace_each / write_utf8!, no host or glue changes.
There's a new examples/file-replace.roc that doubles as the behavioral test — it replaces both occurrences of "Hello" and asserts the exact result, so the replace-each semantics are actually checked (and I verified the assertion fails when the expected string is wrong).
Last updated: Aug 12 2026 at 12:35 UTC