{"id":21323652,"url":"https://github.com/justinethier/zig-mark-sweep-gc","last_synced_at":"2026-02-23T23:13:39.375Z","repository":{"id":136547258,"uuid":"492640437","full_name":"justinethier/zig-mark-sweep-gc","owner":"justinethier","description":":broom: Zig port of Bob Nystrom's simple mark-sweep GC","archived":false,"fork":false,"pushed_at":"2022-05-20T21:03:16.000Z","size":135,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-09T08:38:55.462Z","etag":null,"topics":["garbage-collection","memory-management","zig","ziglang"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/justinethier.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-16T00:49:48.000Z","updated_at":"2023-12-31T18:08:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"08270d2f-b35d-4466-b2a0-94b1eb411829","html_url":"https://github.com/justinethier/zig-mark-sweep-gc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/justinethier/zig-mark-sweep-gc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinethier%2Fzig-mark-sweep-gc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinethier%2Fzig-mark-sweep-gc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinethier%2Fzig-mark-sweep-gc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinethier%2Fzig-mark-sweep-gc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/justinethier","download_url":"https://codeload.github.com/justinethier/zig-mark-sweep-gc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinethier%2Fzig-mark-sweep-gc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29760200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T21:02:23.375Z","status":"ssl_error","status_checked_at":"2026-02-23T20:58:31.539Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["garbage-collection","memory-management","zig","ziglang"],"created_at":"2024-11-21T20:26:04.864Z","updated_at":"2026-02-23T23:13:39.349Z","avatar_url":"https://github.com/justinethier.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Porting a simple Mark-Sweep Garbage Collector to Zig\n\nThis project is a port of Bob Nystrom's simple mark-sweep garbage collector - a modern classic! - from C to Zig. \n\nSince Zig is a relatively new language I thought it would be helpful to give an overview of the experience and talk about the tooling.\n\nThis post explains a few parts of the program but I can't do justice to [Bob's original article](https://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/) which I highly recommend reading. His C implementation is included as `main.c` in this repo and is available [on GitHub](https://github.com/munificent/mark-sweep).\n\nAnyway, off we go...\n\n## Installing Zig\n\nThe installation process is very simple. Just download the latest version from the [Releases page](https://ziglang.org/download/), extract to a folder, and add to your path.\n\nFor example here is how Zig is installed for this project's continuous integration: \n\n```bash\nwget https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz\ntar xf zig-linux*.tar.xz\necho \"`pwd`/zig-linux-x86_64-0.9.1\" \u003e\u003e $GITHUB_PATH\n```\n\nA real installation would put the files somewhere more appropriate but this works just fine for our CI.\n\n`zig` comes as a large statically-linked executable. No dependencies required :thumbsup:.\n\n## Vim Plugin\n\nOut of the box Vim works well enough as an editor for Zig but the experience is a bit underwhelming. So I installed the [Vim configuration for Zig](https://github.com/ziglang/zig.vim) plugin get syntax highlighting.\n\nAs an unexpected bonus:\n\n\u003e This plugin enables automatic code formatting on save by default using `zig fmt`.\n\nMuch like with Go its super handy to be able to just *not worry* about code formatting. On top of that `zig fmt` also catches basic syntax errors:\n\n![Terminal window with syntax error](zig-fmt-syntax-error.webp \"Terminal window with syntax error\")\n\nI tend to save frequently when coding so this provides constant feedback on the state of the code.\n\n## Printing\n\nI just used debug printing to write output:\n\n```zig\nconst print = @import(\"std\").debug.print;\nprint(\"Collected {} objects, {} remaining.\\n\", .{ numObjects - self.numObjects, self.numObjects });\n```\n\nAn interesting observation is that instead of C varargs Zig uses an [anonymous struct](https://ziglang.org/documentation/master/#Anonymous-List-Literals). \n\n## While Loops, If's, Optionals, and More\n\nZig has many improvements over standard C syntax.\n\nIncrement as part of the loop:\n\n```zig\nwhile (i \u003c self.stack_size) : (i += 1) {\n```\n\nUnbox [optionals](https://ziglang.org/documentation/master/#Optionals):\n\n```zig\nwhile (object.*) |obj| {\nif (object.data.pair.head) |head| {\n```\n\nHandle [error unions](https://ziglang.org/documentation/master/#Error-Union-Type):\n\n```zig\nvar a = vm.pushPair() catch unreachable;\n```\n\nOther miscellaneous improvements over C include standard naming conventions (underscores in names for non-callable variables) and a cleaner boolean type.\n\nIt is also helpful to define functions as part of a `struct` type, allowing a more natural organization of code. Though this has been a standard feature in most languages for a long time now. [Including C++](https://stackoverflow.com/a/13125960/101258).\n## Pointers\n\nThe `sweep` function uses a pointer-to-pointer to walk the linked list of all objects and unlink unused objects:\n\n```c\nvoid sweep(VM* vm)\n{\n  Object** object = \u0026vm-\u003efirstObject;\n  while (*object) {\n    if (!(*object)-\u003emarked) {\n      /* This object wasn't reached, so remove it from the list and free it. */\n      Object* unreached = *object;\n\n      *object = unreached-\u003enext;\n      free(unreached);\n\n      vm-\u003enumObjects--;\n    } else {\n      /* This object was reached, so unmark it (for the next GC) and move on to\n       the next. */\n      (*object)-\u003emarked = 0;\n      object = \u0026(*object)-\u003enext;\n    }\n  }\n}\n```\n\nPerhaps surprisingly, this function can be expressed line for line in Zig, just with a fresh new syntax. \n\n```zig\nfn sweep(self: *VM) void {\n    var object = \u0026(self.first_object);\n    while (object.*) |obj| {\n        if (!obj.marked) {\n            // This object wasn't reached, so remove it from the list and free it.\n            var unreached = obj;\n\n            object.* = obj.next;\n            self.allocator.destroy(unreached);\n\n            self.num_objects -= 1;\n        } else {\n            // This object was reached, so unmark it (for the next GC) and move on to\n            // the next.\n            obj.marked = false;\n            object = \u0026(obj.next);\n        }\n    }\n}\n```\n\n## Testing\n\nAll of the tests were ported over to [test declarations](https://ziglang.org/documentation/master/#Zig-Test). These can be invoked via `zig test mark-sweep.zig`:\n\n```zig\ntest \"test 1\" {\n    const allocator = std.testing.allocator;\n    print(\"Test 1: Objects on stack are preserved.\\n\", .{});\n\n    var _vm = try VM.init(allocator);\n    var vm = \u0026_vm;\n    try vm.pushInt(1);\n    try vm.pushInt(2);\n\n    vm.gc();\n\n    try std.testing.expect(vm.num_objects == 2);\n    vm.deinit();\n}\n```\n\nAnother important point! In Zig one does not call `malloc` to allocate memory. Instead one of many allocators is used to allocate memory in the way that is most appropriate for the situation at hand. Our code is organized like a typical library where any allocator may be passed in and retained for memory allocations over the life of a VM instance. \n\nFor testing we use `testing.allocator` which fails the test case if any memory is leaked. This enhances our test as `vm.deinit()` removes all of the [root objects](https://www.memorymanagement.org/glossary/r.html#term-root) and performs a collection. If our code is working properly all allocated memory will be freed.\n\n## Debugging with gdb\n\nHaving debugged C extensively `gdb` I was shocked that it *Just Works* for debugging Zig code: \n\n```shell\njustin@ubuntu:~/Documents/zig-mark-sweep-gc$ make\nzig build-exe mark-sweep.zig\njustin@ubuntu:~/Documents/zig-mark-sweep-gc$ ./mark-sweep \nPerformance Test.\nCollected 20000 objects, 0 remaining.\njustin@ubuntu:~/Documents/zig-mark-sweep-gc$ gdb mark-sweep \nGNU gdb (Ubuntu 9.1-0ubuntu1) 9.1\nCopyright (C) 2020 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later \u003chttp://gnu.org/licenses/gpl.html\u003e\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\nType \"show copying\" and \"show warranty\" for details.\nThis GDB was configured as \"x86_64-linux-gnu\".\nType \"show configuration\" for configuration details.\nFor bug reporting instructions, please see:\n\u003chttp://www.gnu.org/software/gdb/bugs/\u003e.\nFind the GDB manual and other documentation resources online at:\n    \u003chttp://www.gnu.org/software/gdb/documentation/\u003e.\n\nFor help, type \"help\".\nType \"apropos word\" to search for commands related to \"word\"...\nReading symbols from mark-sweep...\n(gdb) break VM.sweep\nBreakpoint 1 at 0x23091c: file /home/justin/Documents/zig-mark-sweep-gc/mark-sweep.zig, line 91.\n(gdb) run\nStarting program: /home/justin/Documents/zig-mark-sweep-gc/mark-sweep \nPerformance Test.\n\nBreakpoint 1, VM.sweep (self=0x7fffffffd938) at /home/justin/Documents/zig-mark-sweep-gc/mark-sweep.zig:91\n91              var object = \u0026(self.first_object);\n(gdb) n\n92              while (object.*) |obj| {\n(gdb) \n```\n\n## Conclusion\n\nThat's it for now. All in all this was a great first experience with Zig and I look forward to writing more Zig in the near future.\n\nUseful links:\n\n- [Zig Language Reference](https://ziglang.org/documentation/master/)\n- [How to read the standard library source code](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code)\n- [Zig Standard Library Documentation](https://ziglang.org/documentation/master/std/)\n- [Zig Tutorial](https://ziglearn.org/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustinethier%2Fzig-mark-sweep-gc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjustinethier%2Fzig-mark-sweep-gc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustinethier%2Fzig-mark-sweep-gc/lists"}