@Anton Yes.
I ran some quick analysis with Claude but did not run the code, please let me know if the suggestions here work for you.
The list from
file_content_bytes_absolute!is fine. The leak is inhostedStdoutWriteBytesinsrc/stdstreams.zig. Roc passesbyte_contenttoStdout.write_bytes!, and that hosted function never releases it. The stack trace points atfromSliceonly because that's where the leaked block was allocated.Hosted functions own their refcounted arguments and must release them before returning. The generated
roc_platform_abi.zigsays this at the top, and also in the doc comment forroc_stdout_write_bytes:/// Owned arguments. Release each exactly once before returning, ... /// arg0.decref(roc_host);Your other stdout/stderr functions already do
defer str.decref(roc_host), so this one just needs the same line:pub fn hostedStdoutWriteBytes(bytes: RocListWith(u8, false)) callconv(.c) StdoutResult { const roc_host = g_roc_host.?; defer bytes.decref(roc_host); ...You don't need to configure anything on the platform side, and no
decrefis needed afterroc_main. If a hosted function returns or stores an argument instead, it passes ownership on rather than releasing it.A few other things Claude noticed while reading:
- The stub functions in
dir.zigand bothfile_create_*functions discard their arguments with_ = str;. They'll leak the same way once a Roc program calls them.hostedCreateAbsoluteBytestakesRocList(RocU8), butRocU8there isabi.RocU8x16, a 16-byte vector. The generated signature isRocListWith(u8, false).hostedContentAbsoluteandhostedContentBytesAbsolutenever close the file they open.
This helped, thank you, much appreciated . It was actually the bug, it did not show up earlier because before I only passed static Roc.Str into the function, makes sense. :heart_hands:
removewingman has marked this topic as resolved.
Last updated: Nov 04 2026 at 18:54 UTC