On current main (and nightly-2026-10-21), keep_if, drop_if, keep_oks and keep_errs are a fold over concat:
keep_if = |list, predicate|
List.fold(list, [], |acc, item| if predicate(item) { List.concat(acc, [item]) } else { acc })
Each kept item allocates a one-item list, and acc grows by reallocating. Keeping all 12 items of a 12-item list costs 13 allocations and 4 reallocs, even when the input list is unique. The doc comment says a unique list is filtered in place with no allocation, and that a fully kept list comes back unchanged. The code doesn't do either. As far as I can tell, keep_if dates from #8501 and never got the reuse work that List.map got in #9610 and #10594.
I hit this when building examples in the Rocco game engine, where view filters entities every frame. In that frame it accounted for 12 allocations and all 4 reallocs.
Proposed fix (I have a local branch with tests):
Results: 12 of 12 kept goes from 13/4 to 1/0 (allocs/reallocs), and a unique list filtered in step goes from 13/4 to 1/0. The full eval suite and rc_conformance pass. The new tests cover a shared list reused afterwards and a predicate that captures the list being filtered, the #10368 shape.
Unlike map's extract/write loop, the in-place path only uses list_swap_unsafe and drop_last, which copy a shared list unless the call is marked in-place. So a wrong uniqueness answer should cost a copy, not a wrong result.
Questions:
Happy to open an issue and PR with this optimisation.
yeah this is what we always should have been doing :sweat_smile:
I think that implementation is probably a very old "just get it working to unblock people" impl that we never got around to revising
PR welcome!
Cool I have put up a PR #12645
Last updated: Nov 04 2026 at 18:54 UTC