There is no function to insert an item into the middle of a list somewhere. An example:
List.insert [0, 1, 2, 3, 4, 5] 2 42 == [0, 1, 42, 2, 3, 4, 5]
There is some discussion here already
Yeah, probably should exist as a zig builtin that just does a memcpy followed by the insert.
I don't think it could be implemented efficiently in roc
I've never implemented a builtin that wasn't pure roc. But I'd like to give it a try!
Will start tomorrow when I have a bit more time. I can just ask for help here right?
yep
I would start by looking at List.append Should be almost identical, but will take an extra arg.
Also, if written in roc, I think the most efficient we could do is:
vs in zig we can:
So in zig, we save an allocation, but likely have a slower copy if the element size is small due to the copy being overlaping. That said, with the zig, we have the option to add back in that allocation for performance if we wanted (but that is an optimization this work should not worry about).
Question about design. Was in the beginners channel, but I'll ask again here.
What if the index is out of bounds? Do we do as update and set do and leave the list unchanged? Or do we return a result? Or do we append instead?
Is a RocList just a Zig ArrayList?
Is a RocList just a Zig ArrayList?
No, but it is relatively similar
What if the index is out of bounds? Do we do as update and set do and leave the list unchanged? Or do we return a result? Or do we append instead?
That is a really solid question. Given we have ? now, I think we should be switching more things to returning results (including probably changing List.set to return a result now). The reason we went without results before is that it was too verbose and painful to use. Given it shouldn't be painful anymore, I definitely lean towards returning more results.
Secondly, I lean towards just making it an append. I feel like a silent insert skip would be super confusing and error prone, much more than setting past the end of a list.
l've started on the function. I just realized how much harder doing LLVM builtins vs pure roc is. I started by reading up on the docs here 'crates/compiler/builtins/README.md'. After that I thought to go ahead and follow List.append as an example. But in the 'crates/compiler/gen_llvm/build_list.rs' file, I have no idea what is going on. There are functions for append and prepend and most other builtins, but I don't understand how I would write one for replace. The difference between the list_append and list_prepend is big, and it all seems arbitrary. A specific question about the difference here would be why does list_prepend have the extra layout_refcounted and inc_alignment_fn arguments?
Is there some information about this? Or do I look around the source a bit more? Maybe continuing this conversation in private with someone might be more useful
@Kilian Vounckx This might just be me and my style, but I love it when people are willing and able to "struggle in public". That means everyone learns what you've learned and it is searchable for the future. And sometimes a random person can drop in mid-way and drop some knowledge that the person who are 1:1 with didn't have.
True, I am getting a bit further, by reading the zig side of things. The inputs in rust make a bit more sense now (still not 100%). Sadly, there is no mention of this in the readme on builtins
The Rust builtins are just for repl right? And Zig builtins are for actual applications?
I haven't really written any rust builtin. If I understand correctly, I have to call zig functions from rust, using some helper function call_list_bitcode_fn_1. But the helper expects other_arguments, which is a slice of arguments. And these have to be in the proper format to be sent to zig. In zig then, you actually implement the builtin
Ah, ok
I'm hitting verry low level LLVM stuff now in 'crates/compiler/gen_dev/src/generic64/mod.rs'. This is a bit over my head at the moment. Will come back to this tomorrow with more time and energy!
Yeah, wiring up a builtin to zig is sadly not the most straight forward thing. While it is not hard after having done it a few times, it kinda touches a lot of things. Thus many tiny changes.
Also, gen_dev is the dev backend.
When I do it, I normally start with a different builtin that is similar and do a lot of find and duplecte with a slightly different name
Yeah I was looking at prepend a lot as a reference
There is definitely a lot that could probably be simplified here.
Also, if you want to test only one backend, the easiest way is to add a test to gen_list and then call cargo test-gen-llvm -- gen_list::new_test_name. Can also do -dev and -wasm.
But it is fine to only implement in the LLM backend for now.
A different pr can add it to the other backends
Last updated: Jun 16 2026 at 16:19 UTC