{"article":{"slug":"borrow-checking-zero-cost-cyclic-mutability","title":"Borrow Checking Zero-Cost Cyclic Mutability","subtitle":null,"summary":"Jake Fecher describes the new design of Ante's borrow checker, which blends group borrowing with his own ideas to relax Rust's aliasing-xor-mutation rule while still statically preventing dangling pointers, enabling safe shared mutability, cyclic mutability and complex data flow at zero runtime cost.","content_type":"blog_post","language":"en","canonical_url":"https://antelang.org/blog/safe_cyclic_mutability/","author":{"name":"Jake Fecher","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Ante","url":"https://antelang.org/","listing_slug":null,"listing":null},"topics":[{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Rust","slug":"rust","url":"https://listedarticles.com/topics/rust"},{"name":"Compilers","slug":"compilers","url":"https://listedarticles.com/topics/compilers"},{"name":"Systems Programming","slug":"systems-programming","url":"https://listedarticles.com/topics/systems-programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3737,"reading_minutes":16,"published_at":"2026-10-19T00:00:00.000Z","added_at":"2026-10-19T14:14:01.067Z","updated_at":"2026-10-19T14:14:01.067Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/borrow-checking-zero-cost-cyclic-mutability","markdown_url":"https://listedarticles.com/articles/borrow-checking-zero-cost-cyclic-mutability.md","example":false,"citation":"Jake Fecher, Ante. \"Borrow Checking Zero-Cost Cyclic Mutability.\" 19 Oct 2026. https://antelang.org/blog/safe_cyclic_mutability/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://antelang.org/blog/safe_cyclic_mutability/"},"body_markdown":"# Borrow Checking Zero-Cost Cyclic Mutability\n\nby Jake Fecher | October 19, 2026\n\nAnte is a low-level[1](#fn:1) functional language built on ownership semantics and borrowing, similar to Rust.\nUnlike Rust, however, a core goal of Ante is to be more lenient in its borrow checker, notably by relaxing Rust’s\nrule that you can either alias a reference or mutate it, but never both at the same time.\n\n```\n// In Rust:\nlet mut my_tuple = (1, 2);\nlet first_elem = &my_tuple.0;\n\n// error[E0506]: cannot assign to `my_tuple` because it is borrowed\nmy_tuple = (3, 4);\nprintln!(\"{first_elem}\");\n```\n\nI wanted to allow for code like the above while still disallowing code that would lead to dangling\npointers, and I wanted to do this entirely statically in the type system.\n\nAlas! That’s old news - the [very first\nblog post](https://antelang.org/blog/safe_shared_mutability/) on this site covered it. That post is frankly quite outdated\nnow. It’s been years since then\nand Ante’s approach has substantially changed, having accrued many additions to make it more flexible.\nNow, it has changed again, having blended many elements from\n[group borrowing](https://verdagon.dev/blog/group-borrowing) with my own ideas to produce a more\nradically safe whole.\nIn this post, I want to go over the new design and show how it leads to a more flexible kind of safe, shared\nmutability that is expressive enough to support *cyclic mutability* and *complex data flow* safely.\n\n---\n\n# Why Shared Mutability?\n\nSince many Rustaceans are skeptical of the value of shared mutability at all, it’s worth reiterating\nwhy being more lenient here is useful. More lenience means:\n\n- Fewer borrow checker errors. Fewer errors both eases the learning curve for\n  new users and speeds up iteration times for experienced ones.\n- More design patterns are allowed. The observer pattern is difficult to get working without shared\n  mutability. Cyclic types can also be more directly represented.\n- Existing design patterns can have fewer workarounds. Fewer forced indices into vectors in particular.\n  While this can be a useful pattern, I don’t believe the language should force users into this approach.\n- More straightforward interop with other languages that also support shared mutability.\n\nThe last point in particular is more important than it seems. Ante is meant to be opt-in to ownership\nto some extent so that by default using it feels more like C# than it does Rust. This is an aspirational\ngoal but allowing shared mutability greatly helps here.\n\n---\n\n# Aside: What was wrong with Ante’s previous rules?\n\nI imagine most will not be familiar with Ante’s old borrow checking rules, but for those curious I’ll\nlay out some more motivations for the change. If this isn’t you, feel free to skip this section.\n\nI am not most readers\n\nThe [original article](https://antelang.org/blog/safe_shared_mutability/) mentions Ante’s `ref` and `mut` references as being\n`&Cell<T>` analogues in a way, and this has largely remained true at its core. Most of the extensions\nmade since then were to extend this in some way, notably by introducing “local uniqueness” where a shared\nmutable reference only needed to be *locally unique* to be used where a `uniq` reference (think `&mut T` in Rust)\nwas expected. This meant the compiler only needed to show that any variable used at the same time as a `mut String`\ndid not mutably alias it. This ended up taking the form of a `Distinct a b` trait which was implemented if\n`a` did not occur anywhere within `b`. For example, if you use a `Vec I32` at the same time a `mut String` is\ntreated as a `uniq String`, the compiler would check a `Distinct String (Vec I32)` constraint in the background.\nSince a `String` can’t occur within a `Vec I32`, the code is allowed and the `String` is locally unique. This\nwould be repeated for each variable used while the converted `uniq String` reference was still alive.\n\nAllowing what is essentially a `&Cell<T>` to `&mut T` conversion like this was great for flexibility but had a few major flaws:\n\n- Common types like `String` would occur within other types more often making them more difficult to use\n  compared to less common types when converted into a `local uniq` reference.\n- `Distinct a b` recurs into every field of `b`, including private fields. This meant adding a private\n  field to a public struct in a library was a breaking change since it could make previously valid local unique\n  conversions invalid.\n- Converting between `mut` and `local uniq` happened often, and was confusing even though the compiler\n  did it automatically.\n- It relied on four reference types: `ref` and `mut` which allow shared mutability, and `imm` and `uniq` which don’t.\n\nIt wasn’t all bad though. Notably, it was powerful enough to encode *safe, cyclic mutability*, with the caveat\nthat some `Rc` values had to be cloned (have their reference count incremented) during traversal.\n\nFor the reasons above though, it was clear the design had to change. The rest of the article is about the\nnew design I eventually landed on which fixes all of these issues, but still keeps the good parts\nlike safe, cyclic mutability[2](#fn:2) and thread-safety.\n\n---\n\n# The Core Problem\n\nWhy are some cases of shared mutability safe, like:\n\n```\nmy_safe_fn (var pair: String, String) =\n    name = ref pair.first\n    pair.first := \"Foo\"\n    println name  // Ok!\n```\n\nWhile others are not:\n\n```\nmy_unsafe_fn (vec: mut Vec t) =\n    elem = vec.get 1\n    vec.clear ()\n    println elem  // Dangling ref!\n```\n\nThe difference is in what I call *shape stability*. If the shape of a type is stable, then there is no way\nto mutate it such that a previously valid reference into it becomes invalid. Picturing the references\nin memory helps here. Let’s look at how `pair` and `name` look in memory:\n\n```\nBefore mutation:\n\n      name\n       |\n       V\npair: ((first_ptr, first_len, ..), (second_ptr, second_len, ..))\n\nAfter mutation:\n\n      name\n       |\n       V\npair: ((first_ptr, first_len, ..), (second_ptr, second_len, ..))\n```\n\nEach `String` is an unboxed tuple of `ptr, len, metadata`, and each component of the\nouter pair is also unboxed. With this layout, there’s no way we could mutate `pair`\nto invalidate `name`!\n\nLooking at `vec` and `elem` however, we see a clear difference:\n\n```\nBefore mutation:\n\nvec: (data_ptr, length, capacity)\n          |\n          V\n          [0, 1, 2, 3]\n              ^\n              |\n             elem\n\nAfter mutation:\n\nvec: (data_ptr, length, capacity)\n          |\n          V\n          []\n              ^\n              |\n             elem\n```\n\n`elem` is clearly dangling! The problem here is that mutating `vec` can mutate `data_ptr`, causing\nit to potentially point elsewhere when it is reallocated. Its shape has changed,\nleaving `elem` stranded.\n\nIt’d be nice if a reference type could remember whether it’s pointing at something stable or\nunstable - then we could do away with uniqueness in favor of a much more accurate restriction that\nrejects fewer valid programs.\n\n---\n\n# Places Over Lifetimes\n\nThe first core part of the puzzle is the use of *places* instead of lifetimes.\n\nUnlike a lifetime which is some arbitrary symbol representing an abstract region of code a\nvariable or reference is valid for, a place is the exact set of things a reference may borrow\nfrom:\n\n```\nfoo (x: I32) (y: I32): Unit =\n    r1: ref 'x I32 = ref x\n    r2: ref 'y I32 = ref y\n\n    r3: ref '(x, y) I32 =\n        if true then ref x else ref y\n```\n\nAbove, `r1` borrows from only `x` so it has the place `'x`. Similarly, `r2` borrows from only `y`\nso it has the place `'y`. `r3` may borrow from `x` or `y` so its place is the union of both: `'(x, y)`.\nPlaces can also be paths such as `'z.bar` to represent a reference borrowing from the struct field\n`bar` on a variable `z`.\n\nPlaces don’t always name variables. There are anonymous places as well such as those for a reference\nto a temporary like `ref 2`, but that’s not important for this article.\n\n> Places make borrow checking easier in general too!\n> Borrow checking in Ante is actually very simple: whenever a value is used, error if any place it\n> refers to has already been moved. There is truthfully\n> a little bit more to it than that, but that really is the core and it can be implemented in only\n> a few lines of code.\n\n---\n\n# Unstable Places\n\nOnce we have places we can model *unstable* places with a simple marker: `!`.\nAn unstable place like `a!` will be invalidated by any mutation to the place `a`.\nThis also means if we have 20 variables with place `a!`, we can mutate them all simultaneously\nsince mutating them would only invalidate variables with place `a!!`.\n\nThis is a bit abstract though, so let’s see how this works in practice. Starting off, we have some\nRust code which should be safe but gets rejected by the compiler:\n\n```\nfn rejected(v: &mut Vec<String>) {\n    // error: cannot borrow `*v` as mutable more than once at a time\n    let (first, last) = (v.first_mut().unwrap(), v.last_mut().unwrap());\n    *first = \"Mutate first\".to_string();\n    println!(\"{last}\");\n}\n```\n\nRust’s compiler rejects this even though there is no way we can mutate `first` which would invalidate `last`.\nTheoretically, even if the first element is also the last, this code should still be valid[3](#fn:3).\n\nWe can model this in Ante by flagging any references to returned elements of a vector as *unstable*:\n\n```\n// Declare a `first_mut` method taking a mutable vector and returning\n// an unstable reference inside 'v to the first element\nVec.first_mut (v: mut Vec t): mut 'v! t can Fail = ...\nVec.last_mut  (v: mut Vec t): mut 'v! t can Fail = ...\n```\n\nNow, we can write the equivalent of `rejected` just fine:\n\n```\nnot_rejected (v: mut Vec String) =\n    first, last = v.first_mut (), v.last_mut ()\n    first.* := \"Mutate first\"\n    println last  // ok!\n```\n\nAbove, `first` and `last` both have the type `mut 'v! String` so when we mutate `first`\nit would prevent us from using anything with a place of `v!!`[4](#fn:4), but nothing does. So we’re\nfree to use both `first` and `last` even if they alias.\n\n> Whether a reference is unstable or not is inferred in practice and doesn’t always need to\n> be a manual choice by users. Any reference through a tagged union or element of a collection\n> type will be unstable for example.\n\n## Loops\n\nLoops and branching in general are fairly straightforward. They largely follow the same rules\nownership has where an error is issued if a variable declared outside of the loop is\ninvalidated within it:\n\n```\nvar v = Vec.of [1, 2, 3]\none = v.get 0\n\nfor _ in 0 .. 3 do\n    println one  // error: one may be invalidated by a later mutation in the for loop\n    v.clear ()   // note: one invalidated here\n```\n\nSimilarly, any closure which invalidates any part of its captured environment is treated\nas a `FnOnce` in Rust terms, preventing it from being called more than a single time.\n\n> Ante has effect handlers too! Control-flow for them is more complex and the rules\n> for how they interact with invalidations of unstable places are [documented here](https://antelang.org/docs/language/#in-traits-and-effect-handlers),\n> but are subject to change (I think they’re a bit hacky). Note that `handle` interactions\n> with unstable places are (at the time of writing) still unimplemented.\n\n---\n\n# Exclusivity\n\nIt’s possible to write functions that are only valid if certain parameters don’t alias:\n\n```\na_and_b_cant_alias (a: mut Vec String) (b: mut Vec String) =\n    elem = a.first_mut ()\n    b.clear ()\n    println elem  // ok! But how?\n```\n\nIn cases like these the compiler still needs some kind of exclusivity rule. In this case,\nwhen a parameter is mutated the compiler needs to ensure the caller of the function gives\na unique value for it:\n\n```\nbad_caller (c: mut Vec String) =\n    // error: 'a cannot alias 'b in this use of a_and_b_cant_alias, but 'c was used for both\n    a_and_b_cant_alias c c\n```\n\nTo check for this, whenever a mutation occurs the compiler issues a `Mutate 'p` effect\nwhich tags a function with the places that may be mutated. At call sites, each place that\nmay be mutated must be distinct from other places instantiated[5](#fn:5). Note that `mut` itself\nstill allows arbitrary aliasing. It is only when an actual mutation occurs and `Mutate 'p` is\nissued does the compiler check call sites that any generic used doesn’t become `'p` while it\nis still in scope.\n\n## Use vs Mention\n\n`Mutate 'p` is an example of a *use* vs *mention* distinction. Code which merely mentions\nmutability isn’t subject to any uniqueness constraints. This is most notable since places\ncan be paths so we may pass an entire `mut Context` to a function but it may only `Mutate 'ctx.field`.\nThis scenario often occurs when iterating mutably over a field of some context value:\n\n```\ntype Entity = name: String, health: I32\ntype Context = entities: Vec Entity, events: Vec String, score: U32\n\nContext.on_death (ctx: mut Context) (entity: ref Entity): Unit\n    can Mutate 'ctx.events\n=\n    ctx.score += 10\n    ctx.events.push \"${entity.name} died\"\n\niterate_example (ctx: mut Context) =\n    for entity in ctx.entities do\n        entity.health -= 1\n        if entity.health <= 0 then\n            ctx.on_death entity  // ok!\n```\n\nIn Rust the above code would be rejected, but there are actually several options in Ante.\nYou can expose `Mutate 'ctx.events` on `Context.on_death` above, guaranteeing only that\nfield may be mutated (`ctx.score` doesn’t need a similar annotation because mutating integers\ncan never invalidate references), or you can tell `on_death` that `entity` may be stored\nwithin `ctx` to begin with:\n\n```\nContext.on_death (ctx: mut Context) (entity: ref 'ctx.entities! Entity): Unit\n    can Mutate 'ctx\n=\n    ctx.score += 10\n    ctx.events.push \"${entity.name} died\"\n```\n\nAbove, we’re saying all of `'ctx` may be mutated. For this specific example, this is less\ndesirable since it’d mean `entity` as an unstable reference wouldn’t be valid after the\ncall to `on_death` ends. For other patterns of code though, we may truly need to mutate\nall of `'ctx` or keep it opaque[6](#fn:6) and this would be exactly what we’d want.\n\n## No Need for Polonius\n\nTaking a bit of a step back, I want to show a bit how powerful it is to reframe\nborrow checking in terms of *places* and *mutations* rather than lifetimes. Because\nlifetimes are tied to a region of code, Rust can struggle with complex data-flow:\n\n```\nfn get_default<'r, K: Hash + Eq + Copy, V: Default>(\n    map: &'r mut HashMap<K, V>,\n    key: K,\n) -> &'r mut V {\n    match map.get_mut(&key) {\n    //    --- first mutable borrow occurs here\n        Some(value) => value,\n        None => {\n            map.insert(key, V::default());\n            //  ^~~~~~ error[E0499]: cannot borrow `*map` as mutable more than once at a time\n            map.get_mut(&key).unwrap()\n        }\n    }\n}\n```\n\nWith Rust’s current model, getting the above code working requires more complex flow\nsensitivity (with the next-gen borrow checker called [Polonius](https://goals.rust-lang.org/2026/polonius.html)).\nIn Ante, it just works:\n\n```\nget_default\n    (map: mut 'r HashMap k v)\n    (key: k)\n    {Hash k} {Eq k} {Copy k} {Default v}\n: mut 'r! v can Mutate 'r, Panic =\n    match try do map.get_mut key\n    | Some value -> value\n    | None ->\n        map.insert key (default ())  // ok!\n        map.get_mut key ~> panic_on_fail\n```\n\nAnte’s borrow checker is based on places and thus doesn’t try tying references\nto any particular region of code. Instead, since the returned element reference from `map.get_mut`\nis unused in the `None` branch, it doesn’t need to be verified at all.\nThis should make some intuitive sense. If the reference is never used, it shouldn’t matter whether\nit’s dangling or not.\n\nLet’s look at another example:\n\n```\nstruct Thing;\n\nimpl Thing {\n    fn maybe_next(&mut self) -> Option<&mut Self> { None }\n}\n\nfn main() {\n    let mut temp = &mut Thing;\n\n    loop {\n        match temp.maybe_next() {\n        //    ^^^^ error: `*temp` was mutably borrowed here in the previous iteration\n        //         of the loop first borrow used here, in later iteration of loop\n            Some(v) => { temp = v; }\n            None => { }\n        }\n    }\n}\n```\n\nAbove, Rust thinks the `maybe_next` borrow may last longer than the loop itself due\nto the `temp = v;` assignment to a variable defined outside of the loop.\n\n```\ntype Thing = x: Unit\n\nThing.maybe_next (_: mut Thing): Maybe (mut Thing) = None\n\nmain () =\n    var temp = mut Thing ()\n\n    while true do\n        match temp.maybe_next ()  // ok!\n        | Some v -> temp := v\n        | None -> ()\n```\n\nMeanwhile, the equivalent Ante code is okay since `temp` gets some anonymous place `'p`,\n`maybe_next` returns the same `'p`, and the mutation would only invalidate any *unstable*\nreferences to `'p`.\n\n---\n\n# Shared Mutability\n\nWe can use unstable places to safely allow mutating shared pointers like an `Rc<T>`\n(reference-counted pointer) in Rust.\n\nThe first realization is that since our analysis is based on places, `Rc` needs a place\nto represent the shared element: `Rc 'p t`. This isn’t needed on other\npointer types like `Box`, but for `Rc`, this extra parameter `'p` is necessary to link\nseparate clones of the same value to the same internal value.\n\nWhen modeling getting a mutable reference into the interior of an `Rc`, we need to make\nuse of a union of places from earlier:\n\n```\nRc.get (rc: mut 'outer (Rc 'inner t)): mut '(outer!, inner) t = ...\n```\n\nThis type says that if we have a mutable reference to an `Rc`, we can get a mutable\nreference to the inner element. This returned reference will have two places: `outer!`\n(unstable because we’re crossing through a shared pointer boundary, and the `Rc` value\nitself may be swapped out), and `inner` - the\nshared value pointed to by every clone of this `Rc`.\n\nLet’s try an example:\n\n```\nmutate_rc_bad (rc1: mut Rc 'p (Vec String)) (rc2: mut Rc 'p (Vec String)) =\n    // string_ref: mut '(rc1!!, p!) String\n    string_ref = rc1.get () |> Vec.first_mut\n\n    // Mutates 'p, so string_ref with unstable 'p! is invalidated\n    // note: string_ref may be invalidated by this mutation\n    rc2.get () |> Vec.clear\n\n    // error: string_ref may have been invalidated by an earlier mutation\n    println string_ref\n```\n\nGreat! We can safely mutate through an `Rc` value with no runtime checks, and the\ncompiler still has our back if we try to do something unsafe. Is this enough\nto represent even cyclic references though?\n\n---\n\n# Cyclic Mutability\n\nFor cyclic mutability we need… well, nothing else! We can just\nstart using `Rc` to form cycles[7](#fn:7).\n\n```\ntype List 'p t =\n   | Nil\n   | Cons t (Rc 'p (List 'p t))\n\nmain () =\n    var f = Rc.of (Cons false (Rc.of Nil))  // false -> []\n    var t = Rc.of (Cons true f)             // true -> false -> []\n    if f is Cons _ tail then\n        // tail's places are '(f!!, a!), so it'd be invalidated by any mutation to f or the shared place 'a.\n        // The latter 'a invalidation is important to prevent us from using it if we mutate any other shared node.\n        tail := t\n\n    emit_list t\n        |> take 5\n        |> iter println  // true, false, true, false, true\n\n// The `do` here is unrelated, it just returns a thunk to be used as a generator function\nemit_list (l: ref List t) = do\n    if l is Cons elem tail then\n        emit elem\n        emit_list tail\n```\n\nThis pattern is very similar to using [GhostCell](https://plv.mpi-sws.org/rustbelt/ghostcell/) in Rust,\nbut in a more ergonomic (no more `GhostToken`), built-in way. I’ll also emphasize that these checks are\n*entirely static* with *no runtime checks*.\n\nIf we wanted, we could even use `shared mut type` sugar to simplify things a bit. See\n[the relevant section in the docs](https://antelang.org/docs/language#shared-mutable-types) for that.\n\n# Note on Multi-threading\n\nWith more permissive shared mutability, how does Ante maintain thread-safety?\n\nThere’s not a very interesting answer here. Ante simply copies Rust’s `Send`/`Sync` traits.\n`ref`/`mut` references both allow for shared mutability and thus implement neither trait.\n\nInstead, Ante has additional reference kinds `imm` and `uniq` which correspond to Rust’s\n`&` and `&mut`. These references are *very rarely used* - they’re more or less only used\nin multi-threaded code where after passing a thread boundary they can degrade back to `ref`/`mut`\nto call functions.\n\n> In the future, the kind of a reference will be attached to the place variable to more easily\n> write code which is polymorphic over any reference kind.\n\n---\n\n# Summary\n\nThis was a lot! Thanks to anyone sticking around to the end! It’s a lot to grok at once but despite the length\nthis article ended up being, I do think the core concepts aren’t *that* complex. There are certainly rules to be\nhad but most are in service of making the language more flexible and making more code “just work.” The main\nmental model is keeping track of the *places* a reference may borrow from, and whether any are unstable.\n\nThis article is mostly a restating of Ante’s [shared mutability rules](https://antelang.org/docs/language#shared-mutability-and-stability)\ndocumentation, but I wanted to write an article version of it to follow up the original shared mutability article and\nto speak on some motivations behind the change.\n\nIf any of this interests you, join [Ante’s Discord](https://discord.gg/NPJncGBAws) to keep up with the language and ask me questions!\nYou can also clone the compiler at [GitHub page](https://github.com/jfecher/ante) to try out the language yourself.\nIf you are interested in the project, the best way to help would be to help spread the project around\nto get some more visibility on it! Contributing would be appreciated as well of course (the compiler is small and I can help)!\n\n---\n\n1. Low-level is always a contentious term. I’m using it here to mean a language with unboxed\n   types by default and no pervasive GC. If you have a preferred definition, let me know! [↩︎](#fnref:1)\n2. The new design is actually more efficient for cyclic mutability! It doesn’t require the clones\n   the previous design did. [↩︎](#fnref:2)\n3. It wouldn’t be sound in Rust since multiple `&mut` references to the same\n   data would violate aliasing assumptions, but it should be in our target semantics! [↩︎](#fnref:3)\n4. `!` on places can nest! `'foo!` is an unstable place within `'foo` while `'foo!!`\n   would be an unstable place within that previous unstable place. This could be, for example, a\n   String element of a `Vec (Vec String)`. If a type has multiple sources of instability (e.g. a\n   struct holding multiple vectors), an API designer may choose to expose the separate fields, e.g.\n   `'foo.bar!` vs `'foo.baz!`, or may choose to hide this by coercing to the parent place `'foo!`. [↩︎](#fnref:4)\n5. Any places that were already `'p` are actually okay. The body of the function\n   would already be aware of and check those cases. This enables patterns like passing\n   in an immutable container and a mutable reference inside the same container. [↩︎](#fnref:5)\n6. Future versions of Ante will allow for ‘abstract places’ where structs could\n   declare a set of places inside of them to expose, separate from their actual fields.\n   Library authors wanting to hide specific struct fields while keeping multiple places\n   within the same struct distinct from each other in usual code could make use of this.\n   The downside is that since these aren’t connected to specific struct fields, it’d be\n   up to the library author to keep the translation to abstract places sound. [↩︎](#fnref:6)\n7. Using `Rc` to form cycles still leads to leaks! Some other collection\n   mechanism is needed to prevent leaks, but that is a separate issue from the\n   mutability of a cycle. [↩︎](#fnref:7)\n","body_html":"<h1 id=\"borrow-checking-zero-cost-cyclic-mutability\">Borrow Checking Zero-Cost Cyclic Mutability</h1>\n<p>by Jake Fecher | October 19, 2026</p>\n<p>Ante is a low-level<a href=\"#fn:1\">1</a> functional language built on ownership semantics and borrowing, similar to Rust.\nUnlike Rust, however, a core goal of Ante is to be more lenient in its borrow checker, notably by relaxing Rust’s\nrule that you can either alias a reference or mutate it, but never both at the same time.</p>\n<pre><code>// In Rust:\nlet mut my_tuple = (1, 2);\nlet first_elem = &amp;my_tuple.0;\n\n// error[E0506]: cannot assign to `my_tuple` because it is borrowed\nmy_tuple = (3, 4);\nprintln!(&quot;{first_elem}&quot;);</code></pre>\n<p>I wanted to allow for code like the above while still disallowing code that would lead to dangling\npointers, and I wanted to do this entirely statically in the type system.</p>\n<p>Alas! That’s old news - the <a href=\"https://antelang.org/blog/safe_shared_mutability/\" rel=\"nofollow ugc noopener\">very first\nblog post</a> on this site covered it. That post is frankly quite outdated\nnow. It’s been years since then\nand Ante’s approach has substantially changed, having accrued many additions to make it more flexible.\nNow, it has changed again, having blended many elements from\n<a href=\"https://verdagon.dev/blog/group-borrowing\" rel=\"nofollow ugc noopener\">group borrowing</a> with my own ideas to produce a more\nradically safe whole.\nIn this post, I want to go over the new design and show how it leads to a more flexible kind of safe, shared\nmutability that is expressive enough to support <em>cyclic mutability</em> and <em>complex data flow</em> safely.</p>\n<hr />\n<h1 id=\"why-shared-mutability\">Why Shared Mutability?</h1>\n<p>Since many Rustaceans are skeptical of the value of shared mutability at all, it’s worth reiterating\nwhy being more lenient here is useful. More lenience means:</p>\n<ul><li><p>Fewer borrow checker errors. Fewer errors both eases the learning curve for</p><p>new users and speeds up iteration times for experienced ones.</p></li><li><p>More design patterns are allowed. The observer pattern is difficult to get working without shared</p><p>mutability. Cyclic types can also be more directly represented.</p></li><li><p>Existing design patterns can have fewer workarounds. Fewer forced indices into vectors in particular.</p><p>While this can be a useful pattern, I don’t believe the language should force users into this approach.</p></li><li>More straightforward interop with other languages that also support shared mutability.</li></ul>\n<p>The last point in particular is more important than it seems. Ante is meant to be opt-in to ownership\nto some extent so that by default using it feels more like C# than it does Rust. This is an aspirational\ngoal but allowing shared mutability greatly helps here.</p>\n<hr />\n<h1 id=\"aside-what-was-wrong-with-ante-s-previous-rules\">Aside: What was wrong with Ante’s previous rules?</h1>\n<p>I imagine most will not be familiar with Ante’s old borrow checking rules, but for those curious I’ll\nlay out some more motivations for the change. If this isn’t you, feel free to skip this section.</p>\n<p>I am not most readers</p>\n<p>The <a href=\"https://antelang.org/blog/safe_shared_mutability/\" rel=\"nofollow ugc noopener\">original article</a> mentions Ante’s <code>ref</code> and <code>mut</code> references as being\n<code>&amp;Cell&lt;T&gt;</code> analogues in a way, and this has largely remained true at its core. Most of the extensions\nmade since then were to extend this in some way, notably by introducing “local uniqueness” where a shared\nmutable reference only needed to be <em>locally unique</em> to be used where a <code>uniq</code> reference (think <code>&amp;mut T</code> in Rust)\nwas expected. This meant the compiler only needed to show that any variable used at the same time as a <code>mut String</code>\ndid not mutably alias it. This ended up taking the form of a <code>Distinct a b</code> trait which was implemented if\n<code>a</code> did not occur anywhere within <code>b</code>. For example, if you use a <code>Vec I32</code> at the same time a <code>mut String</code> is\ntreated as a <code>uniq String</code>, the compiler would check a <code>Distinct String (Vec I32)</code> constraint in the background.\nSince a <code>String</code> can’t occur within a <code>Vec I32</code>, the code is allowed and the <code>String</code> is locally unique. This\nwould be repeated for each variable used while the converted <code>uniq String</code> reference was still alive.</p>\n<p>Allowing what is essentially a <code>&amp;Cell&lt;T&gt;</code> to <code>&amp;mut T</code> conversion like this was great for flexibility but had a few major flaws:</p>\n<ul><li><p>Common types like <code>String</code> would occur within other types more often making them more difficult to use</p><p>compared to less common types when converted into a <code>local uniq</code> reference.</p></li><li><p><code>Distinct a b</code> recurs into every field of <code>b</code>, including private fields. This meant adding a private</p><p>field to a public struct in a library was a breaking change since it could make previously valid local unique\nconversions invalid.</p></li><li><p>Converting between <code>mut</code> and <code>local uniq</code> happened often, and was confusing even though the compiler</p><p>did it automatically.</p></li><li>It relied on four reference types: <code>ref</code> and <code>mut</code> which allow shared mutability, and <code>imm</code> and <code>uniq</code> which don’t.</li></ul>\n<p>It wasn’t all bad though. Notably, it was powerful enough to encode <em>safe, cyclic mutability</em>, with the caveat\nthat some <code>Rc</code> values had to be cloned (have their reference count incremented) during traversal.</p>\n<p>For the reasons above though, it was clear the design had to change. The rest of the article is about the\nnew design I eventually landed on which fixes all of these issues, but still keeps the good parts\nlike safe, cyclic mutability<a href=\"#fn:2\">2</a> and thread-safety.</p>\n<hr />\n<h1 id=\"the-core-problem\">The Core Problem</h1>\n<p>Why are some cases of shared mutability safe, like:</p>\n<pre><code>my_safe_fn (var pair: String, String) =\n    name = ref pair.first\n    pair.first := &quot;Foo&quot;\n    println name  // Ok!</code></pre>\n<p>While others are not:</p>\n<pre><code>my_unsafe_fn (vec: mut Vec t) =\n    elem = vec.get 1\n    vec.clear ()\n    println elem  // Dangling ref!</code></pre>\n<p>The difference is in what I call <em>shape stability</em>. If the shape of a type is stable, then there is no way\nto mutate it such that a previously valid reference into it becomes invalid. Picturing the references\nin memory helps here. Let’s look at how <code>pair</code> and <code>name</code> look in memory:</p>\n<pre><code>Before mutation:\n\n      name\n       |\n       V\npair: ((first_ptr, first_len, ..), (second_ptr, second_len, ..))\n\nAfter mutation:\n\n      name\n       |\n       V\npair: ((first_ptr, first_len, ..), (second_ptr, second_len, ..))</code></pre>\n<p>Each <code>String</code> is an unboxed tuple of <code>ptr, len, metadata</code>, and each component of the\nouter pair is also unboxed. With this layout, there’s no way we could mutate <code>pair</code>\nto invalidate <code>name</code>!</p>\n<p>Looking at <code>vec</code> and <code>elem</code> however, we see a clear difference:</p>\n<pre><code>Before mutation:\n\nvec: (data_ptr, length, capacity)\n          |\n          V\n          [0, 1, 2, 3]\n              ^\n              |\n             elem\n\nAfter mutation:\n\nvec: (data_ptr, length, capacity)\n          |\n          V\n          []\n              ^\n              |\n             elem</code></pre>\n<p><code>elem</code> is clearly dangling! The problem here is that mutating <code>vec</code> can mutate <code>data_ptr</code>, causing\nit to potentially point elsewhere when it is reallocated. Its shape has changed,\nleaving <code>elem</code> stranded.</p>\n<p>It’d be nice if a reference type could remember whether it’s pointing at something stable or\nunstable - then we could do away with uniqueness in favor of a much more accurate restriction that\nrejects fewer valid programs.</p>\n<hr />\n<h1 id=\"places-over-lifetimes\">Places Over Lifetimes</h1>\n<p>The first core part of the puzzle is the use of <em>places</em> instead of lifetimes.</p>\n<p>Unlike a lifetime which is some arbitrary symbol representing an abstract region of code a\nvariable or reference is valid for, a place is the exact set of things a reference may borrow\nfrom:</p>\n<pre><code>foo (x: I32) (y: I32): Unit =\n    r1: ref &#39;x I32 = ref x\n    r2: ref &#39;y I32 = ref y\n\n    r3: ref &#39;(x, y) I32 =\n        if true then ref x else ref y</code></pre>\n<p>Above, <code>r1</code> borrows from only <code>x</code> so it has the place <code>&#39;x</code>. Similarly, <code>r2</code> borrows from only <code>y</code>\nso it has the place <code>&#39;y</code>. <code>r3</code> may borrow from <code>x</code> or <code>y</code> so its place is the union of both: <code>&#39;(x, y)</code>.\nPlaces can also be paths such as <code>&#39;z.bar</code> to represent a reference borrowing from the struct field\n<code>bar</code> on a variable <code>z</code>.</p>\n<p>Places don’t always name variables. There are anonymous places as well such as those for a reference\nto a temporary like <code>ref 2</code>, but that’s not important for this article.</p>\n<blockquote><p>Places make borrow checking easier in general too!\nBorrow checking in Ante is actually very simple: whenever a value is used, error if any place it\nrefers to has already been moved. There is truthfully\na little bit more to it than that, but that really is the core and it can be implemented in only\na few lines of code.</p></blockquote>\n<hr />\n<h1 id=\"unstable-places\">Unstable Places</h1>\n<p>Once we have places we can model <em>unstable</em> places with a simple marker: <code>!</code>.\nAn unstable place like <code>a!</code> will be invalidated by any mutation to the place <code>a</code>.\nThis also means if we have 20 variables with place <code>a!</code>, we can mutate them all simultaneously\nsince mutating them would only invalidate variables with place <code>a!!</code>.</p>\n<p>This is a bit abstract though, so let’s see how this works in practice. Starting off, we have some\nRust code which should be safe but gets rejected by the compiler:</p>\n<pre><code>fn rejected(v: &amp;mut Vec&lt;String&gt;) {\n    // error: cannot borrow `*v` as mutable more than once at a time\n    let (first, last) = (v.first_mut().unwrap(), v.last_mut().unwrap());\n    *first = &quot;Mutate first&quot;.to_string();\n    println!(&quot;{last}&quot;);\n}</code></pre>\n<p>Rust’s compiler rejects this even though there is no way we can mutate <code>first</code> which would invalidate <code>last</code>.\nTheoretically, even if the first element is also the last, this code should still be valid<a href=\"#fn:3\">3</a>.</p>\n<p>We can model this in Ante by flagging any references to returned elements of a vector as <em>unstable</em>:</p>\n<pre><code>// Declare a `first_mut` method taking a mutable vector and returning\n// an unstable reference inside &#39;v to the first element\nVec.first_mut (v: mut Vec t): mut &#39;v! t can Fail = ...\nVec.last_mut  (v: mut Vec t): mut &#39;v! t can Fail = ...</code></pre>\n<p>Now, we can write the equivalent of <code>rejected</code> just fine:</p>\n<pre><code>not_rejected (v: mut Vec String) =\n    first, last = v.first_mut (), v.last_mut ()\n    first.* := &quot;Mutate first&quot;\n    println last  // ok!</code></pre>\n<p>Above, <code>first</code> and <code>last</code> both have the type <code>mut &#39;v! String</code> so when we mutate <code>first</code>\nit would prevent us from using anything with a place of <code>v!!</code><a href=\"#fn:4\">4</a>, but nothing does. So we’re\nfree to use both <code>first</code> and <code>last</code> even if they alias.</p>\n<blockquote><p>Whether a reference is unstable or not is inferred in practice and doesn’t always need to\nbe a manual choice by users. Any reference through a tagged union or element of a collection\ntype will be unstable for example.</p></blockquote>\n<h2 id=\"loops\">Loops</h2>\n<p>Loops and branching in general are fairly straightforward. They largely follow the same rules\nownership has where an error is issued if a variable declared outside of the loop is\ninvalidated within it:</p>\n<pre><code>var v = Vec.of [1, 2, 3]\none = v.get 0\n\nfor _ in 0 .. 3 do\n    println one  // error: one may be invalidated by a later mutation in the for loop\n    v.clear ()   // note: one invalidated here</code></pre>\n<p>Similarly, any closure which invalidates any part of its captured environment is treated\nas a <code>FnOnce</code> in Rust terms, preventing it from being called more than a single time.</p>\n<blockquote><p>Ante has effect handlers too! Control-flow for them is more complex and the rules\nfor how they interact with invalidations of unstable places are <a href=\"https://antelang.org/docs/language/#in-traits-and-effect-handlers\" rel=\"nofollow ugc noopener\">documented here</a>,\nbut are subject to change (I think they’re a bit hacky). Note that <code>handle</code> interactions\nwith unstable places are (at the time of writing) still unimplemented.</p></blockquote>\n<hr />\n<h1 id=\"exclusivity\">Exclusivity</h1>\n<p>It’s possible to write functions that are only valid if certain parameters don’t alias:</p>\n<pre><code>a_and_b_cant_alias (a: mut Vec String) (b: mut Vec String) =\n    elem = a.first_mut ()\n    b.clear ()\n    println elem  // ok! But how?</code></pre>\n<p>In cases like these the compiler still needs some kind of exclusivity rule. In this case,\nwhen a parameter is mutated the compiler needs to ensure the caller of the function gives\na unique value for it:</p>\n<pre><code>bad_caller (c: mut Vec String) =\n    // error: &#39;a cannot alias &#39;b in this use of a_and_b_cant_alias, but &#39;c was used for both\n    a_and_b_cant_alias c c</code></pre>\n<p>To check for this, whenever a mutation occurs the compiler issues a <code>Mutate &#39;p</code> effect\nwhich tags a function with the places that may be mutated. At call sites, each place that\nmay be mutated must be distinct from other places instantiated<a href=\"#fn:5\">5</a>. Note that <code>mut</code> itself\nstill allows arbitrary aliasing. It is only when an actual mutation occurs and <code>Mutate &#39;p</code> is\nissued does the compiler check call sites that any generic used doesn’t become <code>&#39;p</code> while it\nis still in scope.</p>\n<h2 id=\"use-vs-mention\">Use vs Mention</h2>\n<p><code>Mutate &#39;p</code> is an example of a <em>use</em> vs <em>mention</em> distinction. Code which merely mentions\nmutability isn’t subject to any uniqueness constraints. This is most notable since places\ncan be paths so we may pass an entire <code>mut Context</code> to a function but it may only <code>Mutate &#39;ctx.field</code>.\nThis scenario often occurs when iterating mutably over a field of some context value:</p>\n<pre><code>type Entity = name: String, health: I32\ntype Context = entities: Vec Entity, events: Vec String, score: U32\n\nContext.on_death (ctx: mut Context) (entity: ref Entity): Unit\n    can Mutate &#39;ctx.events\n=\n    ctx.score += 10\n    ctx.events.push &quot;${entity.name} died&quot;\n\niterate_example (ctx: mut Context) =\n    for entity in ctx.entities do\n        entity.health -= 1\n        if entity.health &lt;= 0 then\n            ctx.on_death entity  // ok!</code></pre>\n<p>In Rust the above code would be rejected, but there are actually several options in Ante.\nYou can expose <code>Mutate &#39;ctx.events</code> on <code>Context.on_death</code> above, guaranteeing only that\nfield may be mutated (<code>ctx.score</code> doesn’t need a similar annotation because mutating integers\ncan never invalidate references), or you can tell <code>on_death</code> that <code>entity</code> may be stored\nwithin <code>ctx</code> to begin with:</p>\n<pre><code>Context.on_death (ctx: mut Context) (entity: ref &#39;ctx.entities! Entity): Unit\n    can Mutate &#39;ctx\n=\n    ctx.score += 10\n    ctx.events.push &quot;${entity.name} died&quot;</code></pre>\n<p>Above, we’re saying all of <code>&#39;ctx</code> may be mutated. For this specific example, this is less\ndesirable since it’d mean <code>entity</code> as an unstable reference wouldn’t be valid after the\ncall to <code>on_death</code> ends. For other patterns of code though, we may truly need to mutate\nall of <code>&#39;ctx</code> or keep it opaque<a href=\"#fn:6\">6</a> and this would be exactly what we’d want.</p>\n<h2 id=\"no-need-for-polonius\">No Need for Polonius</h2>\n<p>Taking a bit of a step back, I want to show a bit how powerful it is to reframe\nborrow checking in terms of <em>places</em> and <em>mutations</em> rather than lifetimes. Because\nlifetimes are tied to a region of code, Rust can struggle with complex data-flow:</p>\n<pre><code>fn get_default&lt;&#39;r, K: Hash + Eq + Copy, V: Default&gt;(\n    map: &amp;&#39;r mut HashMap&lt;K, V&gt;,\n    key: K,\n) -&gt; &amp;&#39;r mut V {\n    match map.get_mut(&amp;key) {\n    //    --- first mutable borrow occurs here\n        Some(value) =&gt; value,\n        None =&gt; {\n            map.insert(key, V::default());\n            //  ^~~~~~ error[E0499]: cannot borrow `*map` as mutable more than once at a time\n            map.get_mut(&amp;key).unwrap()\n        }\n    }\n}</code></pre>\n<p>With Rust’s current model, getting the above code working requires more complex flow\nsensitivity (with the next-gen borrow checker called <a href=\"https://goals.rust-lang.org/2026/polonius.html\" rel=\"nofollow ugc noopener\">Polonius</a>).\nIn Ante, it just works:</p>\n<pre><code>get_default\n    (map: mut &#39;r HashMap k v)\n    (key: k)\n    {Hash k} {Eq k} {Copy k} {Default v}\n: mut &#39;r! v can Mutate &#39;r, Panic =\n    match try do map.get_mut key\n    | Some value -&gt; value\n    | None -&gt;\n        map.insert key (default ())  // ok!\n        map.get_mut key ~&gt; panic_on_fail</code></pre>\n<p>Ante’s borrow checker is based on places and thus doesn’t try tying references\nto any particular region of code. Instead, since the returned element reference from <code>map.get_mut</code>\nis unused in the <code>None</code> branch, it doesn’t need to be verified at all.\nThis should make some intuitive sense. If the reference is never used, it shouldn’t matter whether\nit’s dangling or not.</p>\n<p>Let’s look at another example:</p>\n<pre><code>struct Thing;\n\nimpl Thing {\n    fn maybe_next(&amp;mut self) -&gt; Option&lt;&amp;mut Self&gt; { None }\n}\n\nfn main() {\n    let mut temp = &amp;mut Thing;\n\n    loop {\n        match temp.maybe_next() {\n        //    ^^^^ error: `*temp` was mutably borrowed here in the previous iteration\n        //         of the loop first borrow used here, in later iteration of loop\n            Some(v) =&gt; { temp = v; }\n            None =&gt; { }\n        }\n    }\n}</code></pre>\n<p>Above, Rust thinks the <code>maybe_next</code> borrow may last longer than the loop itself due\nto the <code>temp = v;</code> assignment to a variable defined outside of the loop.</p>\n<pre><code>type Thing = x: Unit\n\nThing.maybe_next (_: mut Thing): Maybe (mut Thing) = None\n\nmain () =\n    var temp = mut Thing ()\n\n    while true do\n        match temp.maybe_next ()  // ok!\n        | Some v -&gt; temp := v\n        | None -&gt; ()</code></pre>\n<p>Meanwhile, the equivalent Ante code is okay since <code>temp</code> gets some anonymous place <code>&#39;p</code>,\n<code>maybe_next</code> returns the same <code>&#39;p</code>, and the mutation would only invalidate any <em>unstable</em>\nreferences to <code>&#39;p</code>.</p>\n<hr />\n<h1 id=\"shared-mutability\">Shared Mutability</h1>\n<p>We can use unstable places to safely allow mutating shared pointers like an <code>Rc&lt;T&gt;</code>\n(reference-counted pointer) in Rust.</p>\n<p>The first realization is that since our analysis is based on places, <code>Rc</code> needs a place\nto represent the shared element: <code>Rc &#39;p t</code>. This isn’t needed on other\npointer types like <code>Box</code>, but for <code>Rc</code>, this extra parameter <code>&#39;p</code> is necessary to link\nseparate clones of the same value to the same internal value.</p>\n<p>When modeling getting a mutable reference into the interior of an <code>Rc</code>, we need to make\nuse of a union of places from earlier:</p>\n<pre><code>Rc.get (rc: mut &#39;outer (Rc &#39;inner t)): mut &#39;(outer!, inner) t = ...</code></pre>\n<p>This type says that if we have a mutable reference to an <code>Rc</code>, we can get a mutable\nreference to the inner element. This returned reference will have two places: <code>outer!</code>\n(unstable because we’re crossing through a shared pointer boundary, and the <code>Rc</code> value\nitself may be swapped out), and <code>inner</code> - the\nshared value pointed to by every clone of this <code>Rc</code>.</p>\n<p>Let’s try an example:</p>\n<pre><code>mutate_rc_bad (rc1: mut Rc &#39;p (Vec String)) (rc2: mut Rc &#39;p (Vec String)) =\n    // string_ref: mut &#39;(rc1!!, p!) String\n    string_ref = rc1.get () |&gt; Vec.first_mut\n\n    // Mutates &#39;p, so string_ref with unstable &#39;p! is invalidated\n    // note: string_ref may be invalidated by this mutation\n    rc2.get () |&gt; Vec.clear\n\n    // error: string_ref may have been invalidated by an earlier mutation\n    println string_ref</code></pre>\n<p>Great! We can safely mutate through an <code>Rc</code> value with no runtime checks, and the\ncompiler still has our back if we try to do something unsafe. Is this enough\nto represent even cyclic references though?</p>\n<hr />\n<h1 id=\"cyclic-mutability\">Cyclic Mutability</h1>\n<p>For cyclic mutability we need… well, nothing else! We can just\nstart using <code>Rc</code> to form cycles<a href=\"#fn:7\">7</a>.</p>\n<pre><code>type List &#39;p t =\n   | Nil\n   | Cons t (Rc &#39;p (List &#39;p t))\n\nmain () =\n    var f = Rc.of (Cons false (Rc.of Nil))  // false -&gt; []\n    var t = Rc.of (Cons true f)             // true -&gt; false -&gt; []\n    if f is Cons _ tail then\n        // tail&#39;s places are &#39;(f!!, a!), so it&#39;d be invalidated by any mutation to f or the shared place &#39;a.\n        // The latter &#39;a invalidation is important to prevent us from using it if we mutate any other shared node.\n        tail := t\n\n    emit_list t\n        |&gt; take 5\n        |&gt; iter println  // true, false, true, false, true\n\n// The `do` here is unrelated, it just returns a thunk to be used as a generator function\nemit_list (l: ref List t) = do\n    if l is Cons elem tail then\n        emit elem\n        emit_list tail</code></pre>\n<p>This pattern is very similar to using <a href=\"https://plv.mpi-sws.org/rustbelt/ghostcell/\" rel=\"nofollow ugc noopener\">GhostCell</a> in Rust,\nbut in a more ergonomic (no more <code>GhostToken</code>), built-in way. I’ll also emphasize that these checks are\n<em>entirely static</em> with <em>no runtime checks</em>.</p>\n<p>If we wanted, we could even use <code>shared mut type</code> sugar to simplify things a bit. See\n<a href=\"https://antelang.org/docs/language#shared-mutable-types\" rel=\"nofollow ugc noopener\">the relevant section in the docs</a> for that.</p>\n<h1 id=\"note-on-multi-threading\">Note on Multi-threading</h1>\n<p>With more permissive shared mutability, how does Ante maintain thread-safety?</p>\n<p>There’s not a very interesting answer here. Ante simply copies Rust’s <code>Send</code>/<code>Sync</code> traits.\n<code>ref</code>/<code>mut</code> references both allow for shared mutability and thus implement neither trait.</p>\n<p>Instead, Ante has additional reference kinds <code>imm</code> and <code>uniq</code> which correspond to Rust’s\n<code>&amp;</code> and <code>&amp;mut</code>. These references are <em>very rarely used</em> - they’re more or less only used\nin multi-threaded code where after passing a thread boundary they can degrade back to <code>ref</code>/<code>mut</code>\nto call functions.</p>\n<blockquote><p>In the future, the kind of a reference will be attached to the place variable to more easily\nwrite code which is polymorphic over any reference kind.</p></blockquote>\n<hr />\n<h1 id=\"summary\">Summary</h1>\n<p>This was a lot! Thanks to anyone sticking around to the end! It’s a lot to grok at once but despite the length\nthis article ended up being, I do think the core concepts aren’t <em>that</em> complex. There are certainly rules to be\nhad but most are in service of making the language more flexible and making more code “just work.” The main\nmental model is keeping track of the <em>places</em> a reference may borrow from, and whether any are unstable.</p>\n<p>This article is mostly a restating of Ante’s <a href=\"https://antelang.org/docs/language#shared-mutability-and-stability\" rel=\"nofollow ugc noopener\">shared mutability rules</a>\ndocumentation, but I wanted to write an article version of it to follow up the original shared mutability article and\nto speak on some motivations behind the change.</p>\n<p>If any of this interests you, join <a href=\"https://discord.gg/NPJncGBAws\" rel=\"nofollow ugc noopener\">Ante’s Discord</a> to keep up with the language and ask me questions!\nYou can also clone the compiler at <a href=\"https://github.com/jfecher/ante\" rel=\"nofollow ugc noopener\">GitHub page</a> to try out the language yourself.\nIf you are interested in the project, the best way to help would be to help spread the project around\nto get some more visibility on it! Contributing would be appreciated as well of course (the compiler is small and I can help)!</p>\n<hr />\n<ol><li><p>Low-level is always a contentious term. I’m using it here to mean a language with unboxed</p><p> types by default and no pervasive GC. If you have a preferred definition, let me know! <a href=\"#fnref:1\">↩︎</a></p></li><li><p>The new design is actually more efficient for cyclic mutability! It doesn’t require the clones</p><p> the previous design did. <a href=\"#fnref:2\">↩︎</a></p></li><li><p>It wouldn’t be sound in Rust since multiple <code>&amp;mut</code> references to the same</p><p> data would violate aliasing assumptions, but it should be in our target semantics! <a href=\"#fnref:3\">↩︎</a></p></li><li><p><code>!</code> on places can nest! <code>&#39;foo!</code> is an unstable place within <code>&#39;foo</code> while <code>&#39;foo!!</code></p><p> would be an unstable place within that previous unstable place. This could be, for example, a\n String element of a <code>Vec (Vec String)</code>. If a type has multiple sources of instability (e.g. a\n struct holding multiple vectors), an API designer may choose to expose the separate fields, e.g.\n <code>&#39;foo.bar!</code> vs <code>&#39;foo.baz!</code>, or may choose to hide this by coercing to the parent place <code>&#39;foo!</code>. <a href=\"#fnref:4\">↩︎</a></p></li><li><p>Any places that were already <code>&#39;p</code> are actually okay. The body of the function</p><p> would already be aware of and check those cases. This enables patterns like passing\n in an immutable container and a mutable reference inside the same container. <a href=\"#fnref:5\">↩︎</a></p></li><li><p>Future versions of Ante will allow for ‘abstract places’ where structs could</p><p> declare a set of places inside of them to expose, separate from their actual fields.\n Library authors wanting to hide specific struct fields while keeping multiple places\n within the same struct distinct from each other in usual code could make use of this.\n The downside is that since these aren’t connected to specific struct fields, it’d be\n up to the library author to keep the translation to abstract places sound. <a href=\"#fnref:6\">↩︎</a></p></li><li><p>Using <code>Rc</code> to form cycles still leads to leaks! Some other collection</p><p> mechanism is needed to prevent leaks, but that is a separate issue from the\n mutability of a cycle. <a href=\"#fnref:7\">↩︎</a></p></li></ol>","headings":[{"level":1,"text":"Borrow Checking Zero-Cost Cyclic Mutability","id":"borrow-checking-zero-cost-cyclic-mutability"},{"level":1,"text":"Why Shared Mutability?","id":"why-shared-mutability"},{"level":1,"text":"Aside: What was wrong with Ante’s previous rules?","id":"aside-what-was-wrong-with-ante-s-previous-rules"},{"level":1,"text":"The Core Problem","id":"the-core-problem"},{"level":1,"text":"Places Over Lifetimes","id":"places-over-lifetimes"},{"level":1,"text":"Unstable Places","id":"unstable-places"},{"level":2,"text":"Loops","id":"loops"},{"level":1,"text":"Exclusivity","id":"exclusivity"},{"level":2,"text":"Use vs Mention","id":"use-vs-mention"},{"level":2,"text":"No Need for Polonius","id":"no-need-for-polonius"},{"level":1,"text":"Shared Mutability","id":"shared-mutability"},{"level":1,"text":"Cyclic Mutability","id":"cyclic-mutability"},{"level":1,"text":"Note on Multi-threading","id":"note-on-multi-threading"},{"level":1,"text":"Summary","id":"summary"}]}}