{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-18-a-new-way-to-think-about-borrow-checking-in-reverse/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "Travel backwards in time, so that you already know the future\n\nOct 18, 2026  —\n\nHave you ever had a weird idea, and then it worked, but you can't really believe it?\n\nThat's what I'm feeling right now, about Valen's borrow checker.\n\nBecause it **travels backward through time.**\n\nOr, to be less dramatic, it **walks functions backwards,** which is still *very* weird.\n\nYears ago, if you told me it would be good to iterate a function in reverse, I would have called you crazy.\n\nIt turns out, there's actually some really good reasons to do this, and it's fairly common when you're doing e.g. optimization passes. [^1] But I don't think anyone's done this for checking memory safety.\n\nIf you want a really deep look into the world of borrow checking, read on!\n\n*We're about to see some weird things.* [^2]\n\nThis is a pretty deep under-the-hood look into borrow checking algorithms. If you want something epic and entertaining instead, check out my recent post on [The Golden Spike, and Resurrecting the Vale(n) Programming Language](https://verdagon.dev/blog/golden-spike-reviving-vale-valen)!\n\n## A weird kind of borrow checker\n\nThis isn't going to be your normal shared-xor-mutable borrow checking.\n\nThis is [Valen](https://github.com/valen-lang/valen)'s borrow checker, the one that [makes shared-xor-mutable optional](https://verdagon.dev/blog/valen-group-borrowing), and can [call into Rust functions](https://verdagon.dev/blog/golden-spike-reviving-vale-valen) with [memory safety across the boundary](https://verdagon.dev/blog/boundary-memory-safety).\n\nValen is still very experimental. Many things have holes and sharp edges, and likely will until v0.1 comes sometime in February. The glorious months-long refactoring spree is already underway (hence this new borrow checker!). Feel free to check out the [Valen repo](https://github.com/valen-lang/valen) and beware, here be dragons! [2](https://verdagon.dev/blog/reverse-borrow-checking#note2)\n\nIt's kind of like Rust's borrow checker, but it also has memory-safe mutable aliasing. If you want to know more about that, check out [this post](https://verdagon.dev/blog/valen-group-borrowing), though you won't need to understand that for most of this post.\n\n2\n\nGreen dragons, specifically.\n\n## ...and a weird, backwards implementation!\n\nValen compiles a function in four phases: [3](https://verdagon.dev/blog/reverse-borrow-checking#note3)\n\n1. It **parses** the function, turning source text into an AST.\n2. It does **static typing**, figuring out the type for every variable, argument, and expression.\n3. It does **static grouping**, where every reference knows **what variable** (or what *part* of what variable) it's pointing at. [4](https://verdagon.dev/blog/reverse-borrow-checking#note4)\n4. It **safety-checks** the function, to make sure that a reference isn't used after it's invalidated.\n\nI'll explain all of this further below.\n\n**The weirdest part is that #4 happens in reverse.** It looks at the last statement in the function, and then walks backwards until it reaches the top of the function.\n\nThis is weird because, as far as I know, it's the **only borrow checker whose safety check goes backwards.** [5](https://verdagon.dev/blog/reverse-borrow-checking#note5)\n\nFurther below, I'll explain why we're going backwards (hint: convenience, loops, and speed).\n\nBut first, let's see **how it works,** and what it even *means* to go backwards.\n\nLet's skip phase 1, and start with phase 2.\n\n3\n\nThere's also a secret fifth phase, where we calculate what attributes to send to LLVM to describe what operations might change what groups. But that's out of scope for this article.\n\n4\n\nIt also figures out all the groups invalidated by any mut effects from any instructions.\n\n5\n\nFor example:\n\n- Rust (NLL and the current Polonius) does a constraint-solve for region inference, then a **forward** pass to check for conflicts. (Note that rustc does do a backwards scan to figure out where each variable is used, but its actual memory safety checks happen in a forward pass.)\n- Vale's typing pass determines regions, then a later **forward** pass does [region borrow checking](https://verdagon.dev/blog/zero-cost-borrowing-regions-part-1-immutable-borrowing) to determine when it can safely skip run-time [generation checks](https://verdagon.dev/blog/generational-references). (Vale's region borrow checker was actually fused with its instantiator/monomorphizer.)\n- Valen's previous borrow checker did it like Vale too; its typing pass determines groups, then a later **forward** pass would check at every use that it was declared more recently than it was \"invalidated\".\n- There are a lot of other borrow checkers out there, but I haven't heard of any doing their safety checks in reverse. If anyone knows of any, let me know!\n\n### Phase 2: Static Typing\n\nThe static typing pass will be familiar to some of you, it assigns types to variables.\n\nFor example, it turns this program:\n\n```valen\nexported func main() {\n  let fuel = 5;\n  let force = fuel;\n}\n```\n\ninto this program:\n\n```valen\nexported func main() {\n  let fuel i64 = 5;\n  let force i64 = fuel;\n}\n```\n\nThis is pretty normal, lots of compilers do it.\n\nCompilers usually do this as a **forward** pass, walking from the top of the function to the bottom, figuring out types as it goes.\n\nWe're going to do something *similar but weirder* in phase 3; phase 3 is going to do **static grouping.**\n\n### Phase 3: Static Grouping [6](https://verdagon.dev/blog/reverse-borrow-checking#note6)\n\nThis is another forward pass, which figures out where each reference points to.\n\nFor example, in this program:\n\n```valen\nfunc print_first_fuel_cell(s &Ship) {\n  let first_ref = &s.fuel_cells[0];\n  print_int(first_ref);\n}\n```\n\nStatic grouping figures out that first\\_ref is pointing in s.fuel\\_cells\\[\\]: [7](https://verdagon.dev/blog/reverse-borrow-checking#note7)\n\n```valen\nfunc print_first_fuel_cell(s &Ship) {\n  // Static typing says:   first_ref &int\n  // Static grouping says: first_ref &int in s.fuel_cells[]\n  let first_ref = &s.fuel_cells[0];\n  print_int(first_ref);\n}\n```\n\nIn other words, it's pointing to an int somewhere in the s.fuel\\_cells\\[\\] array.\n\nThis information is now *part of the variable's type.*\n\nWe'll be using this information in the next phase, which actually checks for use-after-free.\n\n6\n\nI'll probably start moving away from the term \"group\" soon, switching to \"path\" instead. Opinions welcome!\n\n7\n\nIn V1's static grouping phase, it would also figure out *when* a reference was made. So for example, instead of &int in s.fuel\\_cells\\[\\], it would know that it was &int in s.fuel\\_cells\\[\\] from line 7.2. This was pretty awesome, we were literally **smuggling time and space** information into a variable's type. This new V2 version doesn't need to do that because it does a backwards scan, as we'll see next.\n\n### Phase 4: Safety Checking\n\nThis post is half for fun, but also to **ask for feedback on the algorithm.** I don't think anyone's done it this way before, and there might be a good reason for that. If you see any problems with this approach, please help me out by [leaving a comment](https://www.reddit.com/r/Valen/comments/1x92s1o/a_new_way_to_think_about_borrow_checking_in/), so I can fix (or scrap!) the approach. [8](https://verdagon.dev/blog/reverse-borrow-checking#note8)\n\nNow we can use everything Phase 3 calculated!\n\nThis is the phase that **checks for use-after-free.**\n\nIt does it with a **backwards scan,** *as was foretold.*\n\nLet's see it happen in a slightly bigger example:\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff(self &Entity mut) { ... }\n\nfunc foo(e &Entity mut) {\n  let buff = &e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n```\n\nThis program is doing a **use after free,** and we want the compiler to complain.\n\nFirst, I'll desugar it into a more explicit syntax:\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(e &Entity in g) mut(g) {\n  let buff = &e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n```\n\nNow I'm going to add in some comments, all determined by the backwards scan as it goes.\n\nFurther below, I'll explain what \"DECLARE\", \"USE\", and \"MUTATE\" mean.\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(\n  e &Entity in g          // DECLARE e (group g)\n) mut(g) {\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n```\n\nThe algorithm walks **in reverse,** from the end of the function to the start.\n\nAs it scans upward, it looks for these events:\n\n- When we USE a variable, **add** it to the pending list.\n- When we MUTATE anything, ensure no variables in pending are invalidated.\n- When we DECLARE a variable, **remove** it from the pending list.\n\npending contains all of the things USEd below, that we hope to see DECLAREd above.\n\nWe hope to never see a MUTATE to anything pending.\n\nI think the best way to think of this is that pending contains all variables that **will be used** at some point below/later in the function, and therefore should be valid until then.\n\nTo really understand it, let's see it in action.\n\nFirst, we **look at the last line:**\n\n```valen\n  print_int(buff);        // USE buff (group g.buffs[])\n```\n\nThis is a USE, so we add it to the pending list. pending now has:\n\n- buff (group g.buffs\\[\\])\n\nNow, still going backwards, we encounter the line above it:\n\n```valen\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n```\n\nWe see the MUTATE so we **ensure no variables in pending are invalidated.**\n\nSpecifically, we'll ensure that nothing in pending is pointing at anything under e.\n\n- buff (group g.buffs\\[\\]) ... **This matches!** Show a compile error.\n\nHere's what just happened: buff was pointing at g.buffs\\[\\], which starts with the g we just mutated with add\\_buff. That means that buff is **invalidated above the line where it's used,** which is a use-after-free.\n\nSo the compiler shows an error here. [Huzzah!](https://www.youtube.com/watch?v=y13n1CeyqjY&t=132s)\n\n8\n\nFirst person to break it gets a prize!\n\n### Another Example\n\nLet's see a **correct program,** so we can see the backwards walk encountering a declaration.\n\nHere's the same program, but with e.add\\_buff(); moved up, to be the first line in the function.\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(\n  e &Entity in g          // DECLARE e (group g)\n) mut(g) {\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n```\n\nLike before, we start at the end, and get to this line:\n\n```valen\n  print_int(buff);        // USE buff (group g.buffs[])\n```\n\n...and see that it's a USE so we add buff to the pending list:\n\n- buff (group g.buffs\\[\\])\n\nNow we go one line upward, to this line:\n\n```valen\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n```\n\nThis DECLAREs buff, so we **remove** it from pending.\n\npending is now empty.\n\n(That means that **there are no variables that are used below/later in the function.**)\n\nNow we go one line upward, to this line:\n\n```valen\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n```\n\nWe see the MUTATE, so we **ensure no variables in pending are invalidated.**\n\nThere are no variables in pending, so nothing interesting happens.\n\nThen we see it USE e, so we **add** it to pending:\n\n- e (group g)\n\nAnd that's the end of the function!\n\nWe made it all the way to the end without seeing any MUTATEing any pending variables, which means **the program is memory-safe.**\n\n## Why backwards?\n\nThere are three reasons: convenience, loops, and speed.\n\nGenerally, going backwards is convenient for figuring out **when a thing is last used.**\n\nFor example, look at this function:\n\n```valen\nfunc attack(a &Entity in g, d &Entity in g) mut(g) {\n  let a_power = a.calculate_attack_power();\n  let a_energy_cost = a.calculate_attack_cost(d);\n  let d_armor = d.calculate_defense();\n  let d_energy_cost = d.calculate_defend_cost(a);\n  a.use_energy(a_energy_cost);\n  d.use_energy(d_energy_cost);\n  d.damage(a_power - d_armor);\n}\n```\n\nIf we want to do *bookkeeping X* when a is declared and do *bookkeeping Y* when a is no longer needed, [9](https://verdagon.dev/blog/reverse-borrow-checking#note9) we have two options:\n\n- Do a **forward** scan. We encounter a's declaration, and do X. Then we continue, recording uses of a as we go. Once we hit the end of the function, we **fetch/navigate to** the last recorded location, and do Y.\n- Do a **backward** scan. We encounter the last use of a, and do Y. Then we continue, looking for a's declaration. When we find it, we do X.\n\nNotice how the forward scan involved that extra fetch/navigate, because we couldn't recognize the last use when we encountered it.\n\nThe backward scan was simpler.\n\nIf you want to see another backwards scan help with destructor insertion, check out my [LLVM talk](https://www.youtube.com/watch?v=IpuvQUVB8Cg) from 2024! As many compiler people will tell you, scanning in reverse is useful for a lot of things.\n\nGoing backwards is also better for loops.\n\nHere are a couple examples that I've been using to think about loops: [10](https://verdagon.dev/blog/reverse-borrow-checking#note10)\n\nThis is a correct program, but it shows how tricky it is to know if that observe(e) is valid.\n\n```valen\nexported func main() {\n  let arr = ArrayList<int>(3);\n  let e = &arr[0];\n  \n  while ... {\n    observe(e); // Valid `e` use.\n    add_element(&arr);\n    set e = &arr[0];\n  }\n}\n```\n\nThat e actually can come from two different places:\n\n- The e = &arr\\[0\\]; before the loop.\n- The set e = &arr\\[0\\]; at the end of the loop.\n\nA forward scan encountering observe(e) would need to **look ahead** to the set e to conclude it's valid.\n\nThis is a similar example without the set e.\n\n```valen\nexported func main() {\n  let arr: ArrayList<int> = ...;\n  let e = &arr[0];\n\n  while ... {\n    observe(e); // Invalid `e` use.\n    add_element(&arr);\n\n  }\n}\n```\n\nWithout the set e, the e in the loop is invalidated by a *previous iteration* of the loop.\n\nIf we did a forward scan, we'd need to **look ahead** to see that there's no set e reviving e.\n\nThis is a tricky problem. One can:\n\n- Scan ahead looking for those sets (then jump back).\n- Do repeated forward passes, gaining more information each time, until things settle (a \"dataflow fixpoint\").\n- Do a forward pass to collect the information, and defer checking until later.\n\nThe last one was tempting. Alas, its data structures ended up looking very complex, and it had the same \"fetch/navigate\" problem that we saw in the func attack example above.\n\nIt was simpler to just go straight to V2's reverse-walking approach.\n\nI'm also fairly certain V2's approach is faster, because it only has to process each node once in the phase 3 forward pass, and once in the phase 4 backward pass. [11](https://verdagon.dev/blog/reverse-borrow-checking#note11)\n\nI haven't benchmarked that, so take it with a huge grain of salt. [12](https://verdagon.dev/blog/reverse-borrow-checking#note12)\n\nBut of course, the reverse-walk approach doesn't get loops for free. We still need some adjustments.\n\n9\n\nThis bookkeeping might be decrementing a reference count, or [adding a destructor call](https://www.youtube.com/watch?v=IpuvQUVB8Cg), or checking for use-after-frees, etc.\n\n10\n\nNote, in these examples, I use the name ArrayList, but in the real Valen compiler those are called Array, for weird historical reasons. That'll change soon.\n\n11\n\nNote, this doesn't mean that the algorithm is O(n) yet. The MUTATE still needs to check everything in the pending list. However, I think there's a way to speed that check up a lot. Fingers crossed!\n\n12\n\nV1 never had full loop support, so there's really nothing to benchmark against.\n\n## Reverse-Walking with Loops [13](https://verdagon.dev/blog/reverse-borrow-checking#note13)\n\nThe algorithm needs a little bit more to handle loops.\n\nSpecifically, we'll do MUTATE and DECLARE differently, and we also react to LOOP START.\n\nWe scan upward, looking for these events:\n\n- When we USE a variable, **add** it to the pending list.\n- When we MUTATE anything:\n\n- Ensure no variables in pending are invalidated.\n- Add the mutation to the containing loop's mutations\\_after\\_this list.\n\n- When we DECLARE a variable:\n\n- **remove** it from the pending list.\n- If it wasn't invalidated by anything in mutations\\_after\\_this, add it to the containing loop's revived\\_after\\_this list.\n\n- When we reach a LOOP START:\n\n- For each thing in pending, if it's invalidated by something in the loop's mutations\\_after\\_this, ensure it's also in the loop's revived\\_after\\_this. [14](https://verdagon.dev/blog/reverse-borrow-checking#note14)\n\nLet's see what this does to this example. It's a **correct** program, so there should be no compile error.\n\n```valen\nexported func main() {\n  let arr: ArrayList<int> = ...; // DECLARE arr (group arr)\n  let e = &arr[0];               // DECLARE e (group arr[])\n  \n  while ... {                    // LOOP START\n    observe(e);                  // USE e (group arr[])\n    add_element(&arr);           // MUTATE arr (group arr)\n    set e = &arr[0];             // DECLARE e (group arr[])\n  }\n}\n```\n\nWe're walking backwards, so we start at the last line:\n\n```valen\n    set e = &arr[0];             // DECLARE e (group arr[])\n```\n\nIt's a DECLARE [15](https://verdagon.dev/blog/reverse-borrow-checking#note15) so we do two things:\n\n- **remove** it from the pending list. It's not in it, so nothing happens.\n- If it wasn't invalidated by anything in the containing mutations\\_after\\_this (it wasn't, because it's empty) add it to revived\\_after\\_this.\n\nrevived\\_after\\_this now contains one entry: e.\n\nNow we walk up one line, seeing this:\n\n```valen\n    add_element(&arr);           // MUTATE arr (group arr)\n```\n\nIt's a MUTATE, so we do two things:\n\n- Ensure no variables in pending are invalidated. pending is empty, so nothing happens.\n- Add the mutation to the containing loop's mutations\\_after\\_this list.\n\nmutations\\_after\\_this now contains one entry: arr.\n\nNow we walk up one line, seeing this:\n\n```valen\n    observe(e);                  // USE e (group arr[])\n```\n\nIt's a USE, so we **add** it to the pending list.\n\npending now contains one entry: e (group arr\\[\\]).\n\nNow we walk up one line, seeing the loop start:\n\n```valen\n  while ... {                    // LOOP START\n```\n\nSo we do the LOOP START check: For each thing in pending, if it's invalidated by something in the loop's mutations\\_after\\_this, ensure it's in the loop's revived\\_after\\_this.\n\nIn this case, pending has e (group arr\\[\\]). e *is* invalidated by something in mutations\\_after\\_this. So check it's in the loop's revived\\_after\\_this (it is). Check passed, we're good.\n\nI'll skip the explanation for the top two DECLAREs, since we covered that before.\n\nAnd then we reach the start of the function! We encountered no errors, so the borrow checker can conclude **this function is memory-safe.**\n\nAnd that's it! That's how we handle loops with this new reverse borrow checker.\n\n13\n\nEaster egg note!\n\nSergeant Stubby was the star of the 102nd Infantry Regiment in World War I and travelled with his division to France to fight alongside the French. He served for 18 months and participated in 17 battles and four offensives on the Western Front.\n\nHe received many awards, including a gold medal, a wound stripe, and two purple hearts.\n\nSergeant Stubby has been called the most decorated war dog of the Great War and the only dog to be nominated and promoted to sergeant through combat.\n\n([Source: Wikipedia](https://en.wikipedia.org/wiki/Sergeant_Stubby))\n\nIf you see this note, sneak the word \"gold\" into a [comment](https://www.reddit.com/r/programming/comments/1x92rhh/a_new_way_to_think_about_borrow_checking_in/)!\n\n(Congrats to [timpi4ever](https://www.reddit.com/r/Valen/comments/1x92s1o/comment/phi1ieo/) for finding this first!)\n\n14\n\nWe don't remove anything from pending here, because those pending uses still need to be satisfied by the code above the loop.\n\n15\n\nYes, I regret calling it DECLARE now, since really this is just re-pointing an existing variable.\n\n## More towards Rust's borrow checker?\n\nThere's one more big thing I want to change, eventually.\n\nRust has this very nice ability to use facts from *anywhere* in the function to figure out the lifetime of reference.\n\nFor example, [this Rust program](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=70b5f84ea984e1a3a60dff9e0110bcf9) looks at the later r = &b\\[0\\]; to figure out that r points at a *or* b.\n\n```rs\nfn main() {\n  let a = vec![1, 2, 3];\n  let b = vec![4, 5, 6];\n\n  let mut r = &a[0];\n  if some_condition() {\n    r = &b[0];\n  }\n  println!(\"{}\", r);\n}\n```\n\nValen doesn't do this yet; Valen's phase 3 (static grouping) is still doing normal one-directional inference.\n\nBut if we change phase 3 into a full solve like rustc does, we can get some similar superpowers.\n\nThe neat part: this wouldn't need to change phase 4's backward pass. In the end, we'd have a solver for phase 3, and a backward pass for phase 4.\n\nIt's hard to explain how cool that is to me. It's moments like these I suspect that I might be a compiler nerd. [Huzzah!](https://www.youtube.com/watch?v=y13n1CeyqjY&t=132s)\n\n## That's all for now\n\nValen's borrow checker is still pretty young, a mere 3,600 lines.\n\nAnd it's far from complete! We still have to add a lot of things to it, like interrupting control flow (break, continue), [16](https://verdagon.dev/blog/reverse-borrow-checking#note16) associated groups, closures, [17](https://verdagon.dev/blog/reverse-borrow-checking#note17) all the fun stuff.\n\nWe'll get there one day. That day will be glorious.\n\nThanks for reading!\n\nIn the next few articles, I'm hoping to explain more about how we'll blend in reference counting and generational references. I'm also going to write about all the other languages exploring new memory safety approaches like this ([Ante](https://antelang.org/), [Carbon](https://github.com/carbon-language/carbon-lang), [Zeta](https://github.com/zeta-lang/zeta), [Fang](https://github.com/devast8a/Fang), [exclusion typing](https://dilated.me/blog/posts/introduction-to-exclusion-typing/), and more!).\n\nThis is just the tip of the iceberg, so stay tuned by subscribing to my [RSS feed](https://verdagon.dev/rss.xml), [r/valen](https://www.reddit.com/r/Valen/), or joining the [Valen discord](https://discord.gg/SNB8yGH). And follow me on [Bluesky](https://bsky.app/profile/verdagon.bsky.social) and [Mastodon](https://fosstodon.org/@verdagon)!\n\nCheers,\n\n- Evan Ovadia\n\n16\n\nThe current plan is to first refactor things so the borrow checker is looking at a [control-flow graph](https://en.wikipedia.org/wiki/Control-flow_graph) instead of a typed AST. That should make interrupting control flow much easier.\n\n17\n\nI'm pretty excited about closures. I think I found a *very* elegant way to make them work. Stay tuned!\n\n[^1]: Or [destructor insertion](https://www.youtube.com/watch?v=IpuvQUVB8Cg), and now, checking memory safety!\n\n[^2]: 0% of this article was written by AI. [More thoughts here](https://verdagon.dev/blog/personal-ai-policy).\n\n    My stance: if you want someone to take the time to read something, take the time to write it by hand.\n\n    Thanks for reading =)\n",
  "authors": [
    "verdagon"
  ],
  "captured_at": "2026-10-19T12:57:26.464953148+00:00",
  "category": "news",
  "content_html": "<p>Travel backwards in time, so that you already know the future</p>\n<p>Oct 18, 2026  —</p>\n<p>Have you ever had a weird idea, and then it worked, but you can't really believe it?</p>\n<p>That's what I'm feeling right now, about Valen's borrow checker.</p>\n<p>Because it <strong>travels backward through time.</strong></p>\n<p>Or, to be less dramatic, it <strong>walks functions backwards,</strong> which is still <em>very</em> weird.</p>\n<p>Years ago, if you told me it would be good to iterate a function in reverse, I would have called you crazy.</p>\n<p>It turns out, there's actually some really good reasons to do this, and it's fairly common when you're doing e.g. optimization passes. <sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup> But I don't think anyone's done this for checking memory safety.</p>\n<p>If you want a really deep look into the world of borrow checking, read on!</p>\n<p><em>We're about to see some weird things.</em> <sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup></p>\n<p>This is a pretty deep under-the-hood look into borrow checking algorithms. If you want something epic and entertaining instead, check out my recent post on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/golden-spike-reviving-vale-valen\">The Golden Spike, and Resurrecting the Vale(n) Programming Language</a>!</p>\n<h2>A weird kind of borrow checker</h2>\n<p>This isn't going to be your normal shared-xor-mutable borrow checking.</p>\n<p>This is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/valen-lang/valen\">Valen</a>'s borrow checker, the one that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/valen-group-borrowing\">makes shared-xor-mutable optional</a>, and can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/golden-spike-reviving-vale-valen\">call into Rust functions</a> with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/boundary-memory-safety\">memory safety across the boundary</a>.</p>\n<p>Valen is still very experimental. Many things have holes and sharp edges, and likely will until v0.1 comes sometime in February. The glorious months-long refactoring spree is already underway (hence this new borrow checker!). Feel free to check out the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/valen-lang/valen\">Valen repo</a> and beware, here be dragons! <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note2\">2</a></p>\n<p>It's kind of like Rust's borrow checker, but it also has memory-safe mutable aliasing. If you want to know more about that, check out <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/valen-group-borrowing\">this post</a>, though you won't need to understand that for most of this post.</p>\n<p>2</p>\n<p>Green dragons, specifically.</p>\n<h2>...and a weird, backwards implementation!</h2>\n<p>Valen compiles a function in four phases: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note3\">3</a></p>\n<ol>\n<li>It <strong>parses</strong> the function, turning source text into an AST.</li>\n<li>It does <strong>static typing</strong>, figuring out the type for every variable, argument, and expression.</li>\n<li>It does <strong>static grouping</strong>, where every reference knows <strong>what variable</strong> (or what <em>part</em> of what variable) it's pointing at. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note4\">4</a></li>\n<li>It <strong>safety-checks</strong> the function, to make sure that a reference isn't used after it's invalidated.</li>\n</ol>\n<p>I'll explain all of this further below.</p>\n<p><strong>The weirdest part is that #4 happens in reverse.</strong> It looks at the last statement in the function, and then walks backwards until it reaches the top of the function.</p>\n<p>This is weird because, as far as I know, it's the <strong>only borrow checker whose safety check goes backwards.</strong> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note5\">5</a></p>\n<p>Further below, I'll explain why we're going backwards (hint: convenience, loops, and speed).</p>\n<p>But first, let's see <strong>how it works,</strong> and what it even <em>means</em> to go backwards.</p>\n<p>Let's skip phase 1, and start with phase 2.</p>\n<p>3</p>\n<p>There's also a secret fifth phase, where we calculate what attributes to send to LLVM to describe what operations might change what groups. But that's out of scope for this article.</p>\n<p>4</p>\n<p>It also figures out all the groups invalidated by any mut effects from any instructions.</p>\n<p>5</p>\n<p>For example:</p>\n<ul>\n<li>Rust (NLL and the current Polonius) does a constraint-solve for region inference, then a <strong>forward</strong> pass to check for conflicts. (Note that rustc does do a backwards scan to figure out where each variable is used, but its actual memory safety checks happen in a forward pass.)</li>\n<li>Vale's typing pass determines regions, then a later <strong>forward</strong> pass does <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/zero-cost-borrowing-regions-part-1-immutable-borrowing\">region borrow checking</a> to determine when it can safely skip run-time <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/generational-references\">generation checks</a>. (Vale's region borrow checker was actually fused with its instantiator/monomorphizer.)</li>\n<li>Valen's previous borrow checker did it like Vale too; its typing pass determines groups, then a later <strong>forward</strong> pass would check at every use that it was declared more recently than it was &quot;invalidated&quot;.</li>\n<li>There are a lot of other borrow checkers out there, but I haven't heard of any doing their safety checks in reverse. If anyone knows of any, let me know!</li>\n</ul>\n<h3>Phase 2: Static Typing</h3>\n<p>The static typing pass will be familiar to some of you, it assigns types to variables.</p>\n<p>For example, it turns this program:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">exported func main() {\n  let fuel = 5;\n  let force = fuel;\n}\n</span></code></pre>\n<p>into this program:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">exported func main() {\n  let fuel i64 = 5;\n  let force i64 = fuel;\n}\n</span></code></pre>\n<p>This is pretty normal, lots of compilers do it.</p>\n<p>Compilers usually do this as a <strong>forward</strong> pass, walking from the top of the function to the bottom, figuring out types as it goes.</p>\n<p>We're going to do something <em>similar but weirder</em> in phase 3; phase 3 is going to do <strong>static grouping.</strong></p>\n<h3>Phase 3: Static Grouping <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note6\">6</a></h3>\n<p>This is another forward pass, which figures out where each reference points to.</p>\n<p>For example, in this program:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">func print_first_fuel_cell(s &amp;Ship) {\n  let first_ref = &amp;s.fuel_cells[0];\n  print_int(first_ref);\n}\n</span></code></pre>\n<p>Static grouping figures out that first_ref is pointing in s.fuel_cells[]: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note7\">7</a></p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">func print_first_fuel_cell(s &amp;Ship) {\n  // Static typing says:   first_ref &amp;int\n  // Static grouping says: first_ref &amp;int in s.fuel_cells[]\n  let first_ref = &amp;s.fuel_cells[0];\n  print_int(first_ref);\n}\n</span></code></pre>\n<p>In other words, it's pointing to an int somewhere in the s.fuel_cells[] array.</p>\n<p>This information is now <em>part of the variable's type.</em></p>\n<p>We'll be using this information in the next phase, which actually checks for use-after-free.</p>\n<p>6</p>\n<p>I'll probably start moving away from the term &quot;group&quot; soon, switching to &quot;path&quot; instead. Opinions welcome!</p>\n<p>7</p>\n<p>In V1's static grouping phase, it would also figure out <em>when</em> a reference was made. So for example, instead of &amp;int in s.fuel_cells[], it would know that it was &amp;int in s.fuel_cells[] from line 7.2. This was pretty awesome, we were literally <strong>smuggling time and space</strong> information into a variable's type. This new V2 version doesn't need to do that because it does a backwards scan, as we'll see next.</p>\n<h3>Phase 4: Safety Checking</h3>\n<p>This post is half for fun, but also to <strong>ask for feedback on the algorithm.</strong> I don't think anyone's done it this way before, and there might be a good reason for that. If you see any problems with this approach, please help me out by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.reddit.com/r/Valen/comments/1x92s1o/a_new_way_to_think_about_borrow_checking_in/\">leaving a comment</a>, so I can fix (or scrap!) the approach. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note8\">8</a></p>\n<p>Now we can use everything Phase 3 calculated!</p>\n<p>This is the phase that <strong>checks for use-after-free.</strong></p>\n<p>It does it with a <strong>backwards scan,</strong> <em>as was foretold.</em></p>\n<p>Let's see it happen in a slightly bigger example:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">struct Entity { hp int; buffs []int; }\nfunc add_buff(self &amp;Entity mut) { ... }\n\nfunc foo(e &amp;Entity mut) {\n  let buff = &amp;e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n</span></code></pre>\n<p>This program is doing a <strong>use after free,</strong> and we want the compiler to complain.</p>\n<p>First, I'll desugar it into a more explicit syntax:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">struct Entity { hp int; buffs []int; }\nfunc add_buff&lt;g&#39;&gt;(self &amp;Entity in g) mut(g) { ... }\n\nfunc foo&lt;g&#39;&gt;(e &amp;Entity in g) mut(g) {\n  let buff = &amp;e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n</span></code></pre>\n<p>Now I'm going to add in some comments, all determined by the backwards scan as it goes.</p>\n<p>Further below, I'll explain what &quot;DECLARE&quot;, &quot;USE&quot;, and &quot;MUTATE&quot; mean.</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">struct Entity { hp int; buffs []int; }\nfunc add_buff&lt;g&#39;&gt;(self &amp;Entity in g) mut(g) { ... }\n\nfunc foo&lt;g&#39;&gt;(\n  e &amp;Entity in g          // DECLARE e (group g)\n) mut(g) {\n  let buff = &amp;e.buffs[0]; // DECLARE buff (group g.buffs[])\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n</span></code></pre>\n<p>The algorithm walks <strong>in reverse,</strong> from the end of the function to the start.</p>\n<p>As it scans upward, it looks for these events:</p>\n<ul>\n<li>When we USE a variable, <strong>add</strong> it to the pending list.</li>\n<li>When we MUTATE anything, ensure no variables in pending are invalidated.</li>\n<li>When we DECLARE a variable, <strong>remove</strong> it from the pending list.</li>\n</ul>\n<p>pending contains all of the things USEd below, that we hope to see DECLAREd above.</p>\n<p>We hope to never see a MUTATE to anything pending.</p>\n<p>I think the best way to think of this is that pending contains all variables that <strong>will be used</strong> at some point below/later in the function, and therefore should be valid until then.</p>\n<p>To really understand it, let's see it in action.</p>\n<p>First, we <strong>look at the last line:</strong></p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  print_int(buff);        // USE buff (group g.buffs[])\n</span></code></pre>\n<p>This is a USE, so we add it to the pending list. pending now has:</p>\n<ul>\n<li>buff (group g.buffs[])</li>\n</ul>\n<p>Now, still going backwards, we encounter the line above it:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n</span></code></pre>\n<p>We see the MUTATE so we <strong>ensure no variables in pending are invalidated.</strong></p>\n<p>Specifically, we'll ensure that nothing in pending is pointing at anything under e.</p>\n<ul>\n<li>buff (group g.buffs[]) ... <strong>This matches!</strong> Show a compile error.</li>\n</ul>\n<p>Here's what just happened: buff was pointing at g.buffs[], which starts with the g we just mutated with add_buff. That means that buff is <strong>invalidated above the line where it's used,</strong> which is a use-after-free.</p>\n<p>So the compiler shows an error here. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=y13n1CeyqjY&amp;t=132s\">Huzzah!</a></p>\n<p>8</p>\n<p>First person to break it gets a prize!</p>\n<h3>Another Example</h3>\n<p>Let's see a <strong>correct program,</strong> so we can see the backwards walk encountering a declaration.</p>\n<p>Here's the same program, but with e.add_buff(); moved up, to be the first line in the function.</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">struct Entity { hp int; buffs []int; }\nfunc add_buff&lt;g&#39;&gt;(self &amp;Entity in g) mut(g) { ... }\n\nfunc foo&lt;g&#39;&gt;(\n  e &amp;Entity in g          // DECLARE e (group g)\n) mut(g) {\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  let buff = &amp;e.buffs[0]; // DECLARE buff (group g.buffs[])\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n</span></code></pre>\n<p>Like before, we start at the end, and get to this line:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  print_int(buff);        // USE buff (group g.buffs[])\n</span></code></pre>\n<p>...and see that it's a USE so we add buff to the pending list:</p>\n<ul>\n<li>buff (group g.buffs[])</li>\n</ul>\n<p>Now we go one line upward, to this line:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  let buff = &amp;e.buffs[0]; // DECLARE buff (group g.buffs[])\n</span></code></pre>\n<p>This DECLAREs buff, so we <strong>remove</strong> it from pending.</p>\n<p>pending is now empty.</p>\n<p>(That means that <strong>there are no variables that are used below/later in the function.</strong>)</p>\n<p>Now we go one line upward, to this line:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n</span></code></pre>\n<p>We see the MUTATE, so we <strong>ensure no variables in pending are invalidated.</strong></p>\n<p>There are no variables in pending, so nothing interesting happens.</p>\n<p>Then we see it USE e, so we <strong>add</strong> it to pending:</p>\n<ul>\n<li>e (group g)</li>\n</ul>\n<p>And that's the end of the function!</p>\n<p>We made it all the way to the end without seeing any MUTATEing any pending variables, which means <strong>the program is memory-safe.</strong></p>\n<h2>Why backwards?</h2>\n<p>There are three reasons: convenience, loops, and speed.</p>\n<p>Generally, going backwards is convenient for figuring out <strong>when a thing is last used.</strong></p>\n<p>For example, look at this function:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">func attack(a &amp;Entity in g, d &amp;Entity in g) mut(g) {\n  let a_power = a.calculate_attack_power();\n  let a_energy_cost = a.calculate_attack_cost(d);\n  let d_armor = d.calculate_defense();\n  let d_energy_cost = d.calculate_defend_cost(a);\n  a.use_energy(a_energy_cost);\n  d.use_energy(d_energy_cost);\n  d.damage(a_power - d_armor);\n}\n</span></code></pre>\n<p>If we want to do <em>bookkeeping X</em> when a is declared and do <em>bookkeeping Y</em> when a is no longer needed, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note9\">9</a> we have two options:</p>\n<ul>\n<li>Do a <strong>forward</strong> scan. We encounter a's declaration, and do X. Then we continue, recording uses of a as we go. Once we hit the end of the function, we <strong>fetch/navigate to</strong> the last recorded location, and do Y.</li>\n<li>Do a <strong>backward</strong> scan. We encounter the last use of a, and do Y. Then we continue, looking for a's declaration. When we find it, we do X.</li>\n</ul>\n<p>Notice how the forward scan involved that extra fetch/navigate, because we couldn't recognize the last use when we encountered it.</p>\n<p>The backward scan was simpler.</p>\n<p>If you want to see another backwards scan help with destructor insertion, check out my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=IpuvQUVB8Cg\">LLVM talk</a> from 2024! As many compiler people will tell you, scanning in reverse is useful for a lot of things.</p>\n<p>Going backwards is also better for loops.</p>\n<p>Here are a couple examples that I've been using to think about loops: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note10\">10</a></p>\n<p>This is a correct program, but it shows how tricky it is to know if that observe(e) is valid.</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">exported func main() {\n  let arr = ArrayList&lt;int&gt;(3);\n  let e = &amp;arr[0];\n  \n  while ... {\n    observe(e); // Valid `e` use.\n    add_element(&amp;arr);\n    set e = &amp;arr[0];\n  }\n}\n</span></code></pre>\n<p>That e actually can come from two different places:</p>\n<ul>\n<li>The e = &amp;arr[0]; before the loop.</li>\n<li>The set e = &amp;arr[0]; at the end of the loop.</li>\n</ul>\n<p>A forward scan encountering observe(e) would need to <strong>look ahead</strong> to the set e to conclude it's valid.</p>\n<p>This is a similar example without the set e.</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">exported func main() {\n  let arr: ArrayList&lt;int&gt; = ...;\n  let e = &amp;arr[0];\n\n  while ... {\n    observe(e); // Invalid `e` use.\n    add_element(&amp;arr);\n\n  }\n}\n</span></code></pre>\n<p>Without the set e, the e in the loop is invalidated by a <em>previous iteration</em> of the loop.</p>\n<p>If we did a forward scan, we'd need to <strong>look ahead</strong> to see that there's no set e reviving e.</p>\n<p>This is a tricky problem. One can:</p>\n<ul>\n<li>Scan ahead looking for those sets (then jump back).</li>\n<li>Do repeated forward passes, gaining more information each time, until things settle (a &quot;dataflow fixpoint&quot;).</li>\n<li>Do a forward pass to collect the information, and defer checking until later.</li>\n</ul>\n<p>The last one was tempting. Alas, its data structures ended up looking very complex, and it had the same &quot;fetch/navigate&quot; problem that we saw in the func attack example above.</p>\n<p>It was simpler to just go straight to V2's reverse-walking approach.</p>\n<p>I'm also fairly certain V2's approach is faster, because it only has to process each node once in the phase 3 forward pass, and once in the phase 4 backward pass. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note11\">11</a></p>\n<p>I haven't benchmarked that, so take it with a huge grain of salt. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note12\">12</a></p>\n<p>But of course, the reverse-walk approach doesn't get loops for free. We still need some adjustments.</p>\n<p>9</p>\n<p>This bookkeeping might be decrementing a reference count, or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=IpuvQUVB8Cg\">adding a destructor call</a>, or checking for use-after-frees, etc.</p>\n<p>10</p>\n<p>Note, in these examples, I use the name ArrayList, but in the real Valen compiler those are called Array, for weird historical reasons. That'll change soon.</p>\n<p>11</p>\n<p>Note, this doesn't mean that the algorithm is O(n) yet. The MUTATE still needs to check everything in the pending list. However, I think there's a way to speed that check up a lot. Fingers crossed!</p>\n<p>12</p>\n<p>V1 never had full loop support, so there's really nothing to benchmark against.</p>\n<h2>Reverse-Walking with Loops <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note13\">13</a></h2>\n<p>The algorithm needs a little bit more to handle loops.</p>\n<p>Specifically, we'll do MUTATE and DECLARE differently, and we also react to LOOP START.</p>\n<p>We scan upward, looking for these events:</p>\n<ul>\n<li>\n<p>When we USE a variable, <strong>add</strong> it to the pending list.</p>\n</li>\n<li>\n<p>When we MUTATE anything:</p>\n</li>\n<li>\n<p>Ensure no variables in pending are invalidated.</p>\n</li>\n<li>\n<p>Add the mutation to the containing loop's mutations_after_this list.</p>\n</li>\n<li>\n<p>When we DECLARE a variable:</p>\n</li>\n<li>\n<p><strong>remove</strong> it from the pending list.</p>\n</li>\n<li>\n<p>If it wasn't invalidated by anything in mutations_after_this, add it to the containing loop's revived_after_this list.</p>\n</li>\n<li>\n<p>When we reach a LOOP START:</p>\n</li>\n<li>\n<p>For each thing in pending, if it's invalidated by something in the loop's mutations_after_this, ensure it's also in the loop's revived_after_this. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note14\">14</a></p>\n</li>\n</ul>\n<p>Let's see what this does to this example. It's a <strong>correct</strong> program, so there should be no compile error.</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">exported func main() {\n  let arr: ArrayList&lt;int&gt; = ...; // DECLARE arr (group arr)\n  let e = &amp;arr[0];               // DECLARE e (group arr[])\n  \n  while ... {                    // LOOP START\n    observe(e);                  // USE e (group arr[])\n    add_element(&amp;arr);           // MUTATE arr (group arr)\n    set e = &amp;arr[0];             // DECLARE e (group arr[])\n  }\n}\n</span></code></pre>\n<p>We're walking backwards, so we start at the last line:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">    set e = &amp;arr[0];             // DECLARE e (group arr[])\n</span></code></pre>\n<p>It's a DECLARE <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note15\">15</a> so we do two things:</p>\n<ul>\n<li><strong>remove</strong> it from the pending list. It's not in it, so nothing happens.</li>\n<li>If it wasn't invalidated by anything in the containing mutations_after_this (it wasn't, because it's empty) add it to revived_after_this.</li>\n</ul>\n<p>revived_after_this now contains one entry: e.</p>\n<p>Now we walk up one line, seeing this:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">    add_element(&amp;arr);           // MUTATE arr (group arr)\n</span></code></pre>\n<p>It's a MUTATE, so we do two things:</p>\n<ul>\n<li>Ensure no variables in pending are invalidated. pending is empty, so nothing happens.</li>\n<li>Add the mutation to the containing loop's mutations_after_this list.</li>\n</ul>\n<p>mutations_after_this now contains one entry: arr.</p>\n<p>Now we walk up one line, seeing this:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">    observe(e);                  // USE e (group arr[])\n</span></code></pre>\n<p>It's a USE, so we <strong>add</strong> it to the pending list.</p>\n<p>pending now contains one entry: e (group arr[]).</p>\n<p>Now we walk up one line, seeing the loop start:</p>\n<pre><code class=\"language-valen\"><span class=\"code-snippet\" data-language=\"Valen\">  while ... {                    // LOOP START\n</span></code></pre>\n<p>So we do the LOOP START check: For each thing in pending, if it's invalidated by something in the loop's mutations_after_this, ensure it's in the loop's revived_after_this.</p>\n<p>In this case, pending has e (group arr[]). e <em>is</em> invalidated by something in mutations_after_this. So check it's in the loop's revived_after_this (it is). Check passed, we're good.</p>\n<p>I'll skip the explanation for the top two DECLAREs, since we covered that before.</p>\n<p>And then we reach the start of the function! We encountered no errors, so the borrow checker can conclude <strong>this function is memory-safe.</strong></p>\n<p>And that's it! That's how we handle loops with this new reverse borrow checker.</p>\n<p>13</p>\n<p>Easter egg note!</p>\n<p>Sergeant Stubby was the star of the 102nd Infantry Regiment in World War I and travelled with his division to France to fight alongside the French. He served for 18 months and participated in 17 battles and four offensives on the Western Front.</p>\n<p>He received many awards, including a gold medal, a wound stripe, and two purple hearts.</p>\n<p>Sergeant Stubby has been called the most decorated war dog of the Great War and the only dog to be nominated and promoted to sergeant through combat.</p>\n<p>(<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Sergeant_Stubby\">Source: Wikipedia</a>)</p>\n<p>If you see this note, sneak the word &quot;gold&quot; into a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.reddit.com/r/programming/comments/1x92rhh/a_new_way_to_think_about_borrow_checking_in/\">comment</a>!</p>\n<p>(Congrats to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.reddit.com/r/Valen/comments/1x92s1o/comment/phi1ieo/\">timpi4ever</a> for finding this first!)</p>\n<p>14</p>\n<p>We don't remove anything from pending here, because those pending uses still need to be satisfied by the code above the loop.</p>\n<p>15</p>\n<p>Yes, I regret calling it DECLARE now, since really this is just re-pointing an existing variable.</p>\n<h2>More towards Rust's borrow checker?</h2>\n<p>There's one more big thing I want to change, eventually.</p>\n<p>Rust has this very nice ability to use facts from <em>anywhere</em> in the function to figure out the lifetime of reference.</p>\n<p>For example, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2024&amp;gist=70b5f84ea984e1a3a60dff9e0110bcf9\">this Rust program</a> looks at the later r = &amp;b[0]; to figure out that r points at a <em>or</em> b.</p>\n<pre><code class=\"language-rs\"><span class=\"code-snippet\" data-language=\"Rust\"><span class=\"syntax-source syntax-rust\"><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-storage syntax-type syntax-function syntax-rust\">fn</span> </span><span class=\"syntax-entity syntax-name syntax-function syntax-rust\">main</span></span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-parameters syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-begin syntax-rust\">(</span></span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-parameters syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-end syntax-rust\">)</span></span></span></span><span class=\"syntax-meta syntax-function syntax-rust\"> </span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span>\n  <span class=\"syntax-storage syntax-type syntax-rust\">let</span> a <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-support syntax-macro syntax-rust\">vec!</span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">1</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">2</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">3</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n  <span class=\"syntax-storage syntax-type syntax-rust\">let</span> b <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-support syntax-macro syntax-rust\">vec!</span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">4</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">5</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">6</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n\n  <span class=\"syntax-storage syntax-type syntax-rust\">let</span> <span class=\"syntax-storage syntax-modifier syntax-rust\">mut</span> r <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-keyword syntax-operator syntax-rust\">&amp;</span>a<span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">0</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n  <span class=\"syntax-keyword syntax-control syntax-rust\">if</span> <span class=\"syntax-support syntax-function syntax-rust\">some_condition</span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span></span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span> <span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span>\n    r <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-keyword syntax-operator syntax-rust\">&amp;</span>b<span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-rust\">0</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n  </span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span>\n  <span class=\"syntax-support syntax-macro syntax-rust\">println!</span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span></span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-string syntax-quoted syntax-double syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-rust\">&quot;</span><span class=\"syntax-constant syntax-other syntax-placeholder syntax-rust\">{}</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-rust\">&quot;</span></span></span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> r<span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n</span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span></span>\n</span></span></code></pre>\n<p>Valen doesn't do this yet; Valen's phase 3 (static grouping) is still doing normal one-directional inference.</p>\n<p>But if we change phase 3 into a full solve like rustc does, we can get some similar superpowers.</p>\n<p>The neat part: this wouldn't need to change phase 4's backward pass. In the end, we'd have a solver for phase 3, and a backward pass for phase 4.</p>\n<p>It's hard to explain how cool that is to me. It's moments like these I suspect that I might be a compiler nerd. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=y13n1CeyqjY&amp;t=132s\">Huzzah!</a></p>\n<h2>That's all for now</h2>\n<p>Valen's borrow checker is still pretty young, a mere 3,600 lines.</p>\n<p>And it's far from complete! We still have to add a lot of things to it, like interrupting control flow (break, continue), <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note16\">16</a> associated groups, closures, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/reverse-borrow-checking#note17\">17</a> all the fun stuff.</p>\n<p>We'll get there one day. That day will be glorious.</p>\n<p>Thanks for reading!</p>\n<p>In the next few articles, I'm hoping to explain more about how we'll blend in reference counting and generational references. I'm also going to write about all the other languages exploring new memory safety approaches like this (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://antelang.org/\">Ante</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/carbon-language/carbon-lang\">Carbon</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/zeta-lang/zeta\">Zeta</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/devast8a/Fang\">Fang</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://dilated.me/blog/posts/introduction-to-exclusion-typing/\">exclusion typing</a>, and more!).</p>\n<p>This is just the tip of the iceberg, so stay tuned by subscribing to my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/rss.xml\">RSS feed</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.reddit.com/r/Valen/\">r/valen</a>, or joining the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://discord.gg/SNB8yGH\">Valen discord</a>. And follow me on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://bsky.app/profile/verdagon.bsky.social\">Bluesky</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fosstodon.org/@verdagon\">Mastodon</a>!</p>\n<p>Cheers,</p>\n<ul>\n<li>Evan Ovadia</li>\n</ul>\n<p>16</p>\n<p>The current plan is to first refactor things so the borrow checker is looking at a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Control-flow_graph\">control-flow graph</a> instead of a typed AST. That should make interrupting control flow much easier.</p>\n<p>17</p>\n<p>I'm pretty excited about closures. I think I found a <em>very</em> elegant way to make them work. Stay tuned!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=IpuvQUVB8Cg\">destructor insertion</a>, and now, checking memory safety! <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p>0% of this article was written by AI. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://verdagon.dev/blog/personal-ai-policy\">More thoughts here</a>.</p>\n<p>My stance: if you want someone to take the time to read something, take the time to write it by hand.</p>\n<p>Thanks for reading =) <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n</ol>\n</section>\n",
  "content_markdown": "Travel backwards in time, so that you already know the future\n\nOct 18, 2026  —\n\nHave you ever had a weird idea, and then it worked, but you can't really believe it?\n\nThat's what I'm feeling right now, about Valen's borrow checker.\n\nBecause it **travels backward through time.**\n\nOr, to be less dramatic, it **walks functions backwards,** which is still *very* weird.\n\nYears ago, if you told me it would be good to iterate a function in reverse, I would have called you crazy.\n\nIt turns out, there's actually some really good reasons to do this, and it's fairly common when you're doing e.g. optimization passes. [^1] But I don't think anyone's done this for checking memory safety.\n\nIf you want a really deep look into the world of borrow checking, read on!\n\n*We're about to see some weird things.* [^2]\n\nThis is a pretty deep under-the-hood look into borrow checking algorithms. If you want something epic and entertaining instead, check out my recent post on [The Golden Spike, and Resurrecting the Vale(n) Programming Language](https://verdagon.dev/blog/golden-spike-reviving-vale-valen)!\n\n## A weird kind of borrow checker\n\nThis isn't going to be your normal shared-xor-mutable borrow checking.\n\nThis is [Valen](https://github.com/valen-lang/valen)'s borrow checker, the one that [makes shared-xor-mutable optional](https://verdagon.dev/blog/valen-group-borrowing), and can [call into Rust functions](https://verdagon.dev/blog/golden-spike-reviving-vale-valen) with [memory safety across the boundary](https://verdagon.dev/blog/boundary-memory-safety).\n\nValen is still very experimental. Many things have holes and sharp edges, and likely will until v0.1 comes sometime in February. The glorious months-long refactoring spree is already underway (hence this new borrow checker!). Feel free to check out the [Valen repo](https://github.com/valen-lang/valen) and beware, here be dragons! [2](https://verdagon.dev/blog/reverse-borrow-checking#note2)\n\nIt's kind of like Rust's borrow checker, but it also has memory-safe mutable aliasing. If you want to know more about that, check out [this post](https://verdagon.dev/blog/valen-group-borrowing), though you won't need to understand that for most of this post.\n\n2\n\nGreen dragons, specifically.\n\n## ...and a weird, backwards implementation!\n\nValen compiles a function in four phases: [3](https://verdagon.dev/blog/reverse-borrow-checking#note3)\n\n1. It **parses** the function, turning source text into an AST.\n2. It does **static typing**, figuring out the type for every variable, argument, and expression.\n3. It does **static grouping**, where every reference knows **what variable** (or what *part* of what variable) it's pointing at. [4](https://verdagon.dev/blog/reverse-borrow-checking#note4)\n4. It **safety-checks** the function, to make sure that a reference isn't used after it's invalidated.\n\nI'll explain all of this further below.\n\n**The weirdest part is that #4 happens in reverse.** It looks at the last statement in the function, and then walks backwards until it reaches the top of the function.\n\nThis is weird because, as far as I know, it's the **only borrow checker whose safety check goes backwards.** [5](https://verdagon.dev/blog/reverse-borrow-checking#note5)\n\nFurther below, I'll explain why we're going backwards (hint: convenience, loops, and speed).\n\nBut first, let's see **how it works,** and what it even *means* to go backwards.\n\nLet's skip phase 1, and start with phase 2.\n\n3\n\nThere's also a secret fifth phase, where we calculate what attributes to send to LLVM to describe what operations might change what groups. But that's out of scope for this article.\n\n4\n\nIt also figures out all the groups invalidated by any mut effects from any instructions.\n\n5\n\nFor example:\n\n- Rust (NLL and the current Polonius) does a constraint-solve for region inference, then a **forward** pass to check for conflicts. (Note that rustc does do a backwards scan to figure out where each variable is used, but its actual memory safety checks happen in a forward pass.)\n- Vale's typing pass determines regions, then a later **forward** pass does [region borrow checking](https://verdagon.dev/blog/zero-cost-borrowing-regions-part-1-immutable-borrowing) to determine when it can safely skip run-time [generation checks](https://verdagon.dev/blog/generational-references). (Vale's region borrow checker was actually fused with its instantiator/monomorphizer.)\n- Valen's previous borrow checker did it like Vale too; its typing pass determines groups, then a later **forward** pass would check at every use that it was declared more recently than it was \"invalidated\".\n- There are a lot of other borrow checkers out there, but I haven't heard of any doing their safety checks in reverse. If anyone knows of any, let me know!\n\n### Phase 2: Static Typing\n\nThe static typing pass will be familiar to some of you, it assigns types to variables.\n\nFor example, it turns this program:\n\n```valen\nexported func main() {\n  let fuel = 5;\n  let force = fuel;\n}\n```\n\ninto this program:\n\n```valen\nexported func main() {\n  let fuel i64 = 5;\n  let force i64 = fuel;\n}\n```\n\nThis is pretty normal, lots of compilers do it.\n\nCompilers usually do this as a **forward** pass, walking from the top of the function to the bottom, figuring out types as it goes.\n\nWe're going to do something *similar but weirder* in phase 3; phase 3 is going to do **static grouping.**\n\n### Phase 3: Static Grouping [6](https://verdagon.dev/blog/reverse-borrow-checking#note6)\n\nThis is another forward pass, which figures out where each reference points to.\n\nFor example, in this program:\n\n```valen\nfunc print_first_fuel_cell(s &Ship) {\n  let first_ref = &s.fuel_cells[0];\n  print_int(first_ref);\n}\n```\n\nStatic grouping figures out that first\\_ref is pointing in s.fuel\\_cells\\[\\]: [7](https://verdagon.dev/blog/reverse-borrow-checking#note7)\n\n```valen\nfunc print_first_fuel_cell(s &Ship) {\n  // Static typing says:   first_ref &int\n  // Static grouping says: first_ref &int in s.fuel_cells[]\n  let first_ref = &s.fuel_cells[0];\n  print_int(first_ref);\n}\n```\n\nIn other words, it's pointing to an int somewhere in the s.fuel\\_cells\\[\\] array.\n\nThis information is now *part of the variable's type.*\n\nWe'll be using this information in the next phase, which actually checks for use-after-free.\n\n6\n\nI'll probably start moving away from the term \"group\" soon, switching to \"path\" instead. Opinions welcome!\n\n7\n\nIn V1's static grouping phase, it would also figure out *when* a reference was made. So for example, instead of &int in s.fuel\\_cells\\[\\], it would know that it was &int in s.fuel\\_cells\\[\\] from line 7.2. This was pretty awesome, we were literally **smuggling time and space** information into a variable's type. This new V2 version doesn't need to do that because it does a backwards scan, as we'll see next.\n\n### Phase 4: Safety Checking\n\nThis post is half for fun, but also to **ask for feedback on the algorithm.** I don't think anyone's done it this way before, and there might be a good reason for that. If you see any problems with this approach, please help me out by [leaving a comment](https://www.reddit.com/r/Valen/comments/1x92s1o/a_new_way_to_think_about_borrow_checking_in/), so I can fix (or scrap!) the approach. [8](https://verdagon.dev/blog/reverse-borrow-checking#note8)\n\nNow we can use everything Phase 3 calculated!\n\nThis is the phase that **checks for use-after-free.**\n\nIt does it with a **backwards scan,** *as was foretold.*\n\nLet's see it happen in a slightly bigger example:\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff(self &Entity mut) { ... }\n\nfunc foo(e &Entity mut) {\n  let buff = &e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n```\n\nThis program is doing a **use after free,** and we want the compiler to complain.\n\nFirst, I'll desugar it into a more explicit syntax:\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(e &Entity in g) mut(g) {\n  let buff = &e.buffs[0];\n  e.add_buff();\n  print_int(buff);\n}\n```\n\nNow I'm going to add in some comments, all determined by the backwards scan as it goes.\n\nFurther below, I'll explain what \"DECLARE\", \"USE\", and \"MUTATE\" mean.\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(\n  e &Entity in g          // DECLARE e (group g)\n) mut(g) {\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n```\n\nThe algorithm walks **in reverse,** from the end of the function to the start.\n\nAs it scans upward, it looks for these events:\n\n- When we USE a variable, **add** it to the pending list.\n- When we MUTATE anything, ensure no variables in pending are invalidated.\n- When we DECLARE a variable, **remove** it from the pending list.\n\npending contains all of the things USEd below, that we hope to see DECLAREd above.\n\nWe hope to never see a MUTATE to anything pending.\n\nI think the best way to think of this is that pending contains all variables that **will be used** at some point below/later in the function, and therefore should be valid until then.\n\nTo really understand it, let's see it in action.\n\nFirst, we **look at the last line:**\n\n```valen\n  print_int(buff);        // USE buff (group g.buffs[])\n```\n\nThis is a USE, so we add it to the pending list. pending now has:\n\n- buff (group g.buffs\\[\\])\n\nNow, still going backwards, we encounter the line above it:\n\n```valen\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n```\n\nWe see the MUTATE so we **ensure no variables in pending are invalidated.**\n\nSpecifically, we'll ensure that nothing in pending is pointing at anything under e.\n\n- buff (group g.buffs\\[\\]) ... **This matches!** Show a compile error.\n\nHere's what just happened: buff was pointing at g.buffs\\[\\], which starts with the g we just mutated with add\\_buff. That means that buff is **invalidated above the line where it's used,** which is a use-after-free.\n\nSo the compiler shows an error here. [Huzzah!](https://www.youtube.com/watch?v=y13n1CeyqjY&t=132s)\n\n8\n\nFirst person to break it gets a prize!\n\n### Another Example\n\nLet's see a **correct program,** so we can see the backwards walk encountering a declaration.\n\nHere's the same program, but with e.add\\_buff(); moved up, to be the first line in the function.\n\n```valen\nstruct Entity { hp int; buffs []int; }\nfunc add_buff<g'>(self &Entity in g) mut(g) { ... }\n\nfunc foo<g'>(\n  e &Entity in g          // DECLARE e (group g)\n) mut(g) {\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n  print_int(buff);        // USE buff (group g.buffs[])\n}\n```\n\nLike before, we start at the end, and get to this line:\n\n```valen\n  print_int(buff);        // USE buff (group g.buffs[])\n```\n\n...and see that it's a USE so we add buff to the pending list:\n\n- buff (group g.buffs\\[\\])\n\nNow we go one line upward, to this line:\n\n```valen\n  let buff = &e.buffs[0]; // DECLARE buff (group g.buffs[])\n```\n\nThis DECLAREs buff, so we **remove** it from pending.\n\npending is now empty.\n\n(That means that **there are no variables that are used below/later in the function.**)\n\nNow we go one line upward, to this line:\n\n```valen\n  e.add_buff();           // USE e (group g)\n                          // MUTATE group g\n```\n\nWe see the MUTATE, so we **ensure no variables in pending are invalidated.**\n\nThere are no variables in pending, so nothing interesting happens.\n\nThen we see it USE e, so we **add** it to pending:\n\n- e (group g)\n\nAnd that's the end of the function!\n\nWe made it all the way to the end without seeing any MUTATEing any pending variables, which means **the program is memory-safe.**\n\n## Why backwards?\n\nThere are three reasons: convenience, loops, and speed.\n\nGenerally, going backwards is convenient for figuring out **when a thing is last used.**\n\nFor example, look at this function:\n\n```valen\nfunc attack(a &Entity in g, d &Entity in g) mut(g) {\n  let a_power = a.calculate_attack_power();\n  let a_energy_cost = a.calculate_attack_cost(d);\n  let d_armor = d.calculate_defense();\n  let d_energy_cost = d.calculate_defend_cost(a);\n  a.use_energy(a_energy_cost);\n  d.use_energy(d_energy_cost);\n  d.damage(a_power - d_armor);\n}\n```\n\nIf we want to do *bookkeeping X* when a is declared and do *bookkeeping Y* when a is no longer needed, [9](https://verdagon.dev/blog/reverse-borrow-checking#note9) we have two options:\n\n- Do a **forward** scan. We encounter a's declaration, and do X. Then we continue, recording uses of a as we go. Once we hit the end of the function, we **fetch/navigate to** the last recorded location, and do Y.\n- Do a **backward** scan. We encounter the last use of a, and do Y. Then we continue, looking for a's declaration. When we find it, we do X.\n\nNotice how the forward scan involved that extra fetch/navigate, because we couldn't recognize the last use when we encountered it.\n\nThe backward scan was simpler.\n\nIf you want to see another backwards scan help with destructor insertion, check out my [LLVM talk](https://www.youtube.com/watch?v=IpuvQUVB8Cg) from 2024! As many compiler people will tell you, scanning in reverse is useful for a lot of things.\n\nGoing backwards is also better for loops.\n\nHere are a couple examples that I've been using to think about loops: [10](https://verdagon.dev/blog/reverse-borrow-checking#note10)\n\nThis is a correct program, but it shows how tricky it is to know if that observe(e) is valid.\n\n```valen\nexported func main() {\n  let arr = ArrayList<int>(3);\n  let e = &arr[0];\n  \n  while ... {\n    observe(e); // Valid `e` use.\n    add_element(&arr);\n    set e = &arr[0];\n  }\n}\n```\n\nThat e actually can come from two different places:\n\n- The e = &arr\\[0\\]; before the loop.\n- The set e = &arr\\[0\\]; at the end of the loop.\n\nA forward scan encountering observe(e) would need to **look ahead** to the set e to conclude it's valid.\n\nThis is a similar example without the set e.\n\n```valen\nexported func main() {\n  let arr: ArrayList<int> = ...;\n  let e = &arr[0];\n\n  while ... {\n    observe(e); // Invalid `e` use.\n    add_element(&arr);\n\n  }\n}\n```\n\nWithout the set e, the e in the loop is invalidated by a *previous iteration* of the loop.\n\nIf we did a forward scan, we'd need to **look ahead** to see that there's no set e reviving e.\n\nThis is a tricky problem. One can:\n\n- Scan ahead looking for those sets (then jump back).\n- Do repeated forward passes, gaining more information each time, until things settle (a \"dataflow fixpoint\").\n- Do a forward pass to collect the information, and defer checking until later.\n\nThe last one was tempting. Alas, its data structures ended up looking very complex, and it had the same \"fetch/navigate\" problem that we saw in the func attack example above.\n\nIt was simpler to just go straight to V2's reverse-walking approach.\n\nI'm also fairly certain V2's approach is faster, because it only has to process each node once in the phase 3 forward pass, and once in the phase 4 backward pass. [11](https://verdagon.dev/blog/reverse-borrow-checking#note11)\n\nI haven't benchmarked that, so take it with a huge grain of salt. [12](https://verdagon.dev/blog/reverse-borrow-checking#note12)\n\nBut of course, the reverse-walk approach doesn't get loops for free. We still need some adjustments.\n\n9\n\nThis bookkeeping might be decrementing a reference count, or [adding a destructor call](https://www.youtube.com/watch?v=IpuvQUVB8Cg), or checking for use-after-frees, etc.\n\n10\n\nNote, in these examples, I use the name ArrayList, but in the real Valen compiler those are called Array, for weird historical reasons. That'll change soon.\n\n11\n\nNote, this doesn't mean that the algorithm is O(n) yet. The MUTATE still needs to check everything in the pending list. However, I think there's a way to speed that check up a lot. Fingers crossed!\n\n12\n\nV1 never had full loop support, so there's really nothing to benchmark against.\n\n## Reverse-Walking with Loops [13](https://verdagon.dev/blog/reverse-borrow-checking#note13)\n\nThe algorithm needs a little bit more to handle loops.\n\nSpecifically, we'll do MUTATE and DECLARE differently, and we also react to LOOP START.\n\nWe scan upward, looking for these events:\n\n- When we USE a variable, **add** it to the pending list.\n- When we MUTATE anything:\n\n- Ensure no variables in pending are invalidated.\n- Add the mutation to the containing loop's mutations\\_after\\_this list.\n\n- When we DECLARE a variable:\n\n- **remove** it from the pending list.\n- If it wasn't invalidated by anything in mutations\\_after\\_this, add it to the containing loop's revived\\_after\\_this list.\n\n- When we reach a LOOP START:\n\n- For each thing in pending, if it's invalidated by something in the loop's mutations\\_after\\_this, ensure it's also in the loop's revived\\_after\\_this. [14](https://verdagon.dev/blog/reverse-borrow-checking#note14)\n\nLet's see what this does to this example. It's a **correct** program, so there should be no compile error.\n\n```valen\nexported func main() {\n  let arr: ArrayList<int> = ...; // DECLARE arr (group arr)\n  let e = &arr[0];               // DECLARE e (group arr[])\n  \n  while ... {                    // LOOP START\n    observe(e);                  // USE e (group arr[])\n    add_element(&arr);           // MUTATE arr (group arr)\n    set e = &arr[0];             // DECLARE e (group arr[])\n  }\n}\n```\n\nWe're walking backwards, so we start at the last line:\n\n```valen\n    set e = &arr[0];             // DECLARE e (group arr[])\n```\n\nIt's a DECLARE [15](https://verdagon.dev/blog/reverse-borrow-checking#note15) so we do two things:\n\n- **remove** it from the pending list. It's not in it, so nothing happens.\n- If it wasn't invalidated by anything in the containing mutations\\_after\\_this (it wasn't, because it's empty) add it to revived\\_after\\_this.\n\nrevived\\_after\\_this now contains one entry: e.\n\nNow we walk up one line, seeing this:\n\n```valen\n    add_element(&arr);           // MUTATE arr (group arr)\n```\n\nIt's a MUTATE, so we do two things:\n\n- Ensure no variables in pending are invalidated. pending is empty, so nothing happens.\n- Add the mutation to the containing loop's mutations\\_after\\_this list.\n\nmutations\\_after\\_this now contains one entry: arr.\n\nNow we walk up one line, seeing this:\n\n```valen\n    observe(e);                  // USE e (group arr[])\n```\n\nIt's a USE, so we **add** it to the pending list.\n\npending now contains one entry: e (group arr\\[\\]).\n\nNow we walk up one line, seeing the loop start:\n\n```valen\n  while ... {                    // LOOP START\n```\n\nSo we do the LOOP START check: For each thing in pending, if it's invalidated by something in the loop's mutations\\_after\\_this, ensure it's in the loop's revived\\_after\\_this.\n\nIn this case, pending has e (group arr\\[\\]). e *is* invalidated by something in mutations\\_after\\_this. So check it's in the loop's revived\\_after\\_this (it is). Check passed, we're good.\n\nI'll skip the explanation for the top two DECLAREs, since we covered that before.\n\nAnd then we reach the start of the function! We encountered no errors, so the borrow checker can conclude **this function is memory-safe.**\n\nAnd that's it! That's how we handle loops with this new reverse borrow checker.\n\n13\n\nEaster egg note!\n\nSergeant Stubby was the star of the 102nd Infantry Regiment in World War I and travelled with his division to France to fight alongside the French. He served for 18 months and participated in 17 battles and four offensives on the Western Front.\n\nHe received many awards, including a gold medal, a wound stripe, and two purple hearts.\n\nSergeant Stubby has been called the most decorated war dog of the Great War and the only dog to be nominated and promoted to sergeant through combat.\n\n([Source: Wikipedia](https://en.wikipedia.org/wiki/Sergeant_Stubby))\n\nIf you see this note, sneak the word \"gold\" into a [comment](https://www.reddit.com/r/programming/comments/1x92rhh/a_new_way_to_think_about_borrow_checking_in/)!\n\n(Congrats to [timpi4ever](https://www.reddit.com/r/Valen/comments/1x92s1o/comment/phi1ieo/) for finding this first!)\n\n14\n\nWe don't remove anything from pending here, because those pending uses still need to be satisfied by the code above the loop.\n\n15\n\nYes, I regret calling it DECLARE now, since really this is just re-pointing an existing variable.\n\n## More towards Rust's borrow checker?\n\nThere's one more big thing I want to change, eventually.\n\nRust has this very nice ability to use facts from *anywhere* in the function to figure out the lifetime of reference.\n\nFor example, [this Rust program](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=70b5f84ea984e1a3a60dff9e0110bcf9) looks at the later r = &b\\[0\\]; to figure out that r points at a *or* b.\n\n```rs\nfn main() {\n  let a = vec![1, 2, 3];\n  let b = vec![4, 5, 6];\n\n  let mut r = &a[0];\n  if some_condition() {\n    r = &b[0];\n  }\n  println!(\"{}\", r);\n}\n```\n\nValen doesn't do this yet; Valen's phase 3 (static grouping) is still doing normal one-directional inference.\n\nBut if we change phase 3 into a full solve like rustc does, we can get some similar superpowers.\n\nThe neat part: this wouldn't need to change phase 4's backward pass. In the end, we'd have a solver for phase 3, and a backward pass for phase 4.\n\nIt's hard to explain how cool that is to me. It's moments like these I suspect that I might be a compiler nerd. [Huzzah!](https://www.youtube.com/watch?v=y13n1CeyqjY&t=132s)\n\n## That's all for now\n\nValen's borrow checker is still pretty young, a mere 3,600 lines.\n\nAnd it's far from complete! We still have to add a lot of things to it, like interrupting control flow (break, continue), [16](https://verdagon.dev/blog/reverse-borrow-checking#note16) associated groups, closures, [17](https://verdagon.dev/blog/reverse-borrow-checking#note17) all the fun stuff.\n\nWe'll get there one day. That day will be glorious.\n\nThanks for reading!\n\nIn the next few articles, I'm hoping to explain more about how we'll blend in reference counting and generational references. I'm also going to write about all the other languages exploring new memory safety approaches like this ([Ante](https://antelang.org/), [Carbon](https://github.com/carbon-language/carbon-lang), [Zeta](https://github.com/zeta-lang/zeta), [Fang](https://github.com/devast8a/Fang), [exclusion typing](https://dilated.me/blog/posts/introduction-to-exclusion-typing/), and more!).\n\nThis is just the tip of the iceberg, so stay tuned by subscribing to my [RSS feed](https://verdagon.dev/rss.xml), [r/valen](https://www.reddit.com/r/Valen/), or joining the [Valen discord](https://discord.gg/SNB8yGH). And follow me on [Bluesky](https://bsky.app/profile/verdagon.bsky.social) and [Mastodon](https://fosstodon.org/@verdagon)!\n\nCheers,\n\n- Evan Ovadia\n\n16\n\nThe current plan is to first refactor things so the borrow checker is looking at a [control-flow graph](https://en.wikipedia.org/wiki/Control-flow_graph) instead of a typed AST. That should make interrupting control flow much easier.\n\n17\n\nI'm pretty excited about closures. I think I found a *very* elegant way to make them work. Stay tuned!\n\n[^1]: Or [destructor insertion](https://www.youtube.com/watch?v=IpuvQUVB8Cg), and now, checking memory safety!\n\n[^2]: 0% of this article was written by AI. [More thoughts here](https://verdagon.dev/blog/personal-ai-policy).\n\n    My stance: if you want someone to take the time to read something, take the time to write it by hand.\n\n    Thanks for reading =)\n",
  "dateCreated": "2026-10-19T12:57:26.464953148+00:00",
  "date_created": "2026-10-18T12:11:13+00:00",
  "date_modified": "2026-10-18T12:11:13+00:00",
  "date_published": "2026-10-18T12:11:13+00:00",
  "description": "Travel backwards in time, so that you already know the future Oct 18, 2026 — Have you ever had a weird idea, and then it worked, but you can't really believe it? That's what I'm feeling right now, about Valen's borrow checker. Because it…",
  "external_url": "https://verdagon.dev/blog/reverse-borrow-checking",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/hnrss-org/2026/10/2026-10-18-a-new-way-to-think-about-borrow-checking-in-reverse.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/hnrss-org/2026/10/2026-10-18-a-new-way-to-think-about-borrow-checking-in-reverse.md"
  },
  "id": "urn:aggr:item:26b708ed41c57bc956662f5f3f06c34009a606d0",
  "isBasedOn": {
    "@id": "https://verdagon.dev/blog/reverse-borrow-checking"
  },
  "mainEntity": {
    "@id": "https://verdagon.dev/blog/reverse-borrow-checking",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-18-a-new-way-to-think-about-borrow-checking-in-reverse/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "verdagon"
      }
    ],
    "dateModified": "2026-10-18T12:11:13+00:00",
    "datePublished": "2026-10-18T12:11:13+00:00",
    "headline": "A new way to think about borrow checking (in reverse)",
    "timeRequired": "PT16M",
    "url": "https://verdagon.dev/blog/reverse-borrow-checking",
    "wordCount": 3395
  },
  "name": "Archived snapshot: A new way to think about borrow checking (in reverse)",
  "preview": null,
  "reading_minutes": 16,
  "replicated_at": null,
  "source": "hnrss-org",
  "source_name": "Hacker News: Front Page",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-19T12:57:26.464953148+00:00",
  "title": "A new way to think about borrow checking (in reverse)",
  "url": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-18-a-new-way-to-think-about-borrow-checking-in-reverse/",
  "word_count": 3395
}