{"id":50897785,"url":"https://github.com/MilesCranmer/BorrowChecker.jl","last_synced_at":"2026-07-03T16:01:26.710Z","repository":{"id":271157687,"uuid":"912556639","full_name":"MilesCranmer/BorrowChecker.jl","owner":"MilesCranmer","description":"A borrow checker for Julia","archived":false,"fork":false,"pushed_at":"2026-06-11T00:37:08.000Z","size":1421,"stargazers_count":141,"open_issues_count":8,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-06-11T01:18:56.342Z","etag":null,"topics":["borrow-checker","code-quality","concurrency","development","development-tools","julia","memory-management","memory-safety","multithreading","ownership","programming-language","rust","safety","testing","thread-safety"],"latest_commit_sha":null,"homepage":"https://ai.damtp.cam.ac.uk/borrowcheckerjl/dev/","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MilesCranmer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-05T22:33:05.000Z","updated_at":"2026-06-10T23:46:27.000Z","dependencies_parsed_at":"2025-05-20T11:06:06.024Z","dependency_job_id":"e4d176a3-9702-444f-9ba9-066676de4cb8","html_url":"https://github.com/MilesCranmer/BorrowChecker.jl","commit_stats":null,"previous_names":["milescranmer/borrowchecker.jl"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/MilesCranmer/BorrowChecker.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MilesCranmer%2FBorrowChecker.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MilesCranmer%2FBorrowChecker.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MilesCranmer%2FBorrowChecker.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MilesCranmer%2FBorrowChecker.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MilesCranmer","download_url":"https://codeload.github.com/MilesCranmer/BorrowChecker.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MilesCranmer%2FBorrowChecker.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35092185,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["borrow-checker","code-quality","concurrency","development","development-tools","julia","memory-management","memory-safety","multithreading","ownership","programming-language","rust","safety","testing","thread-safety"],"created_at":"2026-06-16T01:31:30.079Z","updated_at":"2026-07-03T16:01:26.682Z","avatar_url":"https://github.com/MilesCranmer.png","language":"Julia","funding_links":[],"categories":["code-quality"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"https://github.com/user-attachments/assets/b68b4d0e-7bec-4876-a39d-5edf3191a8d9\" width=\"500\"\u003e\n\n# BorrowChecker.jl\n\n[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://astroautomata.com/BorrowChecker.jl/dev)\n[![Build Status](https://github.com/MilesCranmer/BorrowChecker.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/MilesCranmer/BorrowChecker.jl/actions/workflows/CI.yml?query=branch%3Amain)\n[![Coverage](https://coveralls.io/repos/github/MilesCranmer/BorrowChecker.jl/badge.svg?branch=main)](https://coveralls.io/github/MilesCranmer/BorrowChecker.jl?branch=main)\n\n\u003c/div\u003e\n\nThis is an experimental package for emulating a runtime borrow checker in Julia, using a macro layer over regular code. This is built to mimic Rust's ownership, lifetime, and borrowing semantics. This tool is mainly to be used in development and testing to flag memory safety issues, and help you design safer code.\n\nBorrowChecker.jl currently has two alternative layers:\n\n1. **Automatic checking (`BorrowChecker.@safe`)**\n   - Drop-in for existing Julia code: wrap a function and BorrowChecker will run a best-effort borrow check when that method specialization executes.\n   - It does not change program behavior (except for throwing when it finds a violation).\n2. **Manual overlay (explicit ownership/borrow macros like `@own`, `@ref`, `@take!`, …)**\n   - More explicit and safer, but much more effort and invasive.\n   - More feature complete.\n\nIn Julia, when you write `x = [1, 2, 3]`, the actual _object_ exists completely independently of the variable, and you can refer to it from as many variables as you want without issue:\n\n```julia\nx = [1, 2, 3]\ny = x\nprintln(length(x))\n# 3\n```\n\nOnce there are no more references to the object, the \"garbage collector\" will work to free the memory.\n\nRust is much different. For example, the equivalent code is **invalid** in Rust\n\n```rust\nlet x = vec![1, 2, 3];\nlet y = x;\nprintln!(\"{}\", x.len());\n// error[E0382]: borrow of moved value: `x`\n```\n\nRust refuses to compile this code. Why? Because in Rust, objects (`vec![1, 2, 3]`) are _owned_ by variables. When you write `let y = x`, the ownership of `vec![1, 2, 3]` is _moved_ to `y`. Now `x` is no longer allowed to access it.\n\nTo fix this, we would either write\n\n```rust\nlet y = x.clone();\n// OR\nlet y = \u0026x;\n```\n\nto either create a copy of the vector, or _borrow_ `x` using the `\u0026` operator to create a reference. You can create as many references as you want, but there can only be one original object.\n\nThis \"ownership\" paradigm can help improve safety of code. Especially in complex, multithreaded codebases, it is easy to shoot yourself in the foot and modify objects which are \"owned\" (editable) by something else. Rust's ownership and lifetime model makes it so that you can _prove_ memory safety of code! Standard thread races are literally impossible. (Assuming you are not using `unsafe { ... }` to disable safety features, or the borrow checker itself has a bug, etc.)\n\nIn BorrowChecker.jl, we demonstrate an implementation of some of these ideas. The aim is to build a development layer that can help prevent a few classes of memory safety issues, without affecting runtime behavior of code.\n\n## Automatic Checking: `BorrowChecker.@safe`\n\n`BorrowChecker.@safe` automatically instruments a function by analyzing the compiler IR and runs a best-effort borrow check at runtime. This requires Julia ≥ 1.12.\n\n\u003e [!WARNING]\n\u003e This macro is highly experimental and compiler-dependent. There are likely bugs and false positives. It is intended for development and testing, and does not guarantee memory safety.\n\n### Options\n\n`@safe` supports a few options that are compiled into a `BorrowChecker.Auto.Config`:\n\n- `scope` (default `:function`): whether to recursively borrow-check callees (`:none`, `:function`, `:module`, `:user`, `:all`).\n- `max_summary_depth` (default `12`): recursion depth limit for effect summarization when effects cannot be directly resolved.\n- `optimize_until` (default varies): which compiler pass to stop at when fetching IR (`Base.code_ircode_by_type`).\n\n`scope` meanings:\n\n- `:none`: disable `@safe` entirely.\n- `:function`: check only the annotated method.\n- `:module`: recursively check callees defined in the module where `@safe` is used.\n- `:user`: recursively check callees, but ignore `Core` and `Base` (including their submodules).\n- `:all`: recursively check callees across all modules (very aggressive).\n\nThe `@safe` checked-cache is keyed by specialization *and these options*, so checking a function once under `scope=:function` will not incorrectly skip a later recursive check under `scope=:module` / `:all`.\n\n`@safe` is meant to be a *drop-in tripwire* for existing code:\n\n- **Aliasing violations**: mutating a value while another live binding may observe that mutation.\n- **Escapes / \"moves\"**: storing a mutable value somewhere that outlives the current scope (e.g. a global cache / a field / a container), then continuing to reference it locally.\n\nThis analyzes the compiler’s IR, so it can catch patterns that are \"hidden\" by lowering (keyword calls, closure captures, views, etc.). It is intentionally **best-effort**: when it cannot determine what a call does, it will be conservative (and may throw false positives). For code where you want a stronger, explicit model, use the manual overlay macros below.\n\n### How it works\n\nWhen you write:\n\n```julia\nBorrowChecker.@safe function f(args...)\n    # ...\nend\n```\n\nthe macro rewrites the function so that:\n\n1. **On entry**, it runs a borrow check for the *current method specialization* (e.g. `f(::Vector{Int})`), and caches the result (so future calls are faster).\n2. The checker asks Julia for the function's **typed compiler IR** (the lowered form the compiler optimizes).\n3. It walks that IR and tracks two key things:\n   - Which bindings may refer to the **same mutable object** (aliasing).\n   - Which operations **write** to a tracked object or cause it to **escape** (be treated like a move).\n4. When it sees an operation that would be illegal under Rust-like rules (e.g. \"write while aliased\", or \"use after escape\"), it throws a `BorrowCheckError` with a source-level-ish diagnostic.\n\n### Aliasing Detection\n\nBorrowChecker.jl's `@safe` macro can detect when values are modified through aliased bindings, and throw an error:\n\n```julia\njulia\u003e import BorrowChecker\n\njulia\u003e BorrowChecker.@safe function f()\n           x = [1, 2, 3]\n           y = x\n           push!(x, 4)\n           return y\n       end\nf (generic function with 1 method)\n\njulia\u003e f()  # errors\n```\n\nThis will generate a helpful error pointing out the location of the borrow check violation, and the statement that violated the rule:\n\n```\nERROR: BorrowCheckError for specialization Tuple{typeof(f)}\n\n  method: f() @ Main REPL[7]:1\n\n  [1] stmt#7: cannot perform write: value is aliased by another live binding      at REPL[7]:4\n        2         x = [1, 2, 3]\n        3         y = x\n      \u003e 4         push!(x, 4)\n        5         return y\n        6     end\n\n      stmt: Main.push!(%5, 4)\n\n```\n\nTo fix it, simply copy the value, which will avoid the error:\n\n```julia\njulia\u003e BorrowChecker.@safe function f()\n           x = [1, 2, 3]\n           y = copy(x)\n           push!(x, 4)\n           return y\n       end\nf (generic function with 1 method)\n\njulia\u003e f()\n3-element Vector{Int64}:\n 1\n 2\n 3\n```\n\n### Escape Detection\n\nMuch like Rust's ownership model, BorrowChecker.jl's `@safe` macro attempts to infer when values escape their scope (moved/consumed) and throw an error if they are used afterwards.\n\n```julia\njulia\u003e const CACHE = Dict()\nDict{Any, Any}()\n\njulia\u003e foo(x) = (CACHE[x] = 1; nothing)\nfoo (generic function with 1 method)\n\njulia\u003e BorrowChecker.@safe function bar()\n           x = [1, 2]\n           foo(x)\n           return x\n       end\nbar (generic function with 1 method)\n\njulia\u003e bar()  # errors\n```\n\nThis generates the following error:\n\n```\nERROR: BorrowCheckError for specialization Tuple{typeof(bar)}\n\n  method: bar() @ Main REPL[13]:1\n\n  [1] stmt#6: value escapes/consumed by unknown call; it (or an alias) is used later      at REPL[13]:3\n        1     BorrowChecker.@safe function bar()\n        2         x = [1, 2]\n      \u003e 3         foo(x)\n        4         return x\n        5     end\n\n      stmt: Main.foo(%5)\n```\n\nWhy is this an error? Because `x` was stored as a key in the cache, but is _mutable externally_. Furthermore, it is returned by `bar`! This is a violation of borrowing rules. Once the value gets stored in the cache, its ownership is _transferred_ to the cache, and is no longer accessible by `bar`. So this example is illegal.\n\nHow can we fix it? We have two options. The first is we can copy the value before storing it:\n\n```julia\njulia\u003e BorrowChecker.@safe function bar()\n           x = [1, 2]\n           foo(copy(x))\n           return x\n       end\nbar (generic function with 1 method)\n\njulia\u003e bar()  # ok\n```\n\nWe no longer have access to the object created by `copy(x)`, so the borrow check passes.\nAlternatively, we can use immutable objects, which are safe to pass around:\n\n```julia\njulia\u003e BorrowChecker.@safe function bar()\n           x = (1, 2)\n           foo(x)\n           return x\n       end\nbar (generic function with 1 method)\n\njulia\u003e bar()  # ok\n```\n\n### More `@safe` examples\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003e@safe\u003c/code\u003e analyzes the entire callstack\u003c/summary\u003e\n\nBorrowChecker doesn't rely on naming conventions, such as the presence of `!` in the function name. It tries to infer effects from IR:\n\n```julia\njulia\u003e h(x) = (push!(x, 1); nothing)  # no \"!\" in the name\nh (generic function with 1 method)\n\njulia\u003e BorrowChecker.@safe function demo()\n           x = [1, 2, 3]\n           y = x\n           h(x)\n           return y\n       end\ndemo (generic function with 1 method)\n\njulia\u003e demo()  # errors\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eKeyword arguments are handled (the checker sees lowered \u003ccode\u003ekwcall\u003c/code\u003e IR)\u003c/summary\u003e\n\nKeyword calls get lowered into a `NamedTuple` + `Core.kwcall(...)`. `@safe` analyzes the lowered IR, so aliasing via keyword arguments is still visible:\n\n```julia\njulia\u003e f(; x, y) = (push!(x, 1); push!(y, 1); x .+ y)\nf (generic function with 1 method)\n\njulia\u003e BorrowChecker.@safe function kw_demo()\n           x = [1, 2, 3]\n           y = x\n           return sum(f(; x=x, y=y))\n       end\nkw_demo (generic function with 1 method)\n\njulia\u003e kw_demo()  # errors\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAliasing isn't only \u003ccode\u003ey = x\u003c/code\u003e: views can alias too\u003c/summary\u003e\n\n```julia\njulia\u003e BorrowChecker.@safe function view_demo()\n           x = [1, 2, 3, 4]\n           y = view(x, 1:2)  # aliases x\n           push!(x, 9)\n           return collect(y)\n       end\nview_demo (generic function with 1 method)\n\njulia\u003e view_demo()  # errors\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eClosures are analyzed too\u003c/summary\u003e\n\n```julia\njulia\u003e BorrowChecker.@safe function closure_demo()\n           x = [1, 2, 3]\n           y = x\n           f = () -\u003e (push!(x, 9); nothing)\n           f()\n           return y\n       end\nclosure_demo (generic function with 1 method)\n\njulia\u003e closure_demo()  # errors\n```\n\nRead-only captures are typically fine.\n\u003c/details\u003e\n\n## Manual Overlay (explicit ownership/borrow macros)\n\nAlternatively, we can use the manual overlay macros to achieve a more explicit effect. This is much more invasive, but might be useful in teaching you how to think about ownership and borrowing.\n\nThe early Rust example, with BorrowChecker.jl, would look like this:\n\n```julia\nusing BorrowChecker\n\n@own x = [1, 2, 3]\n@own y = x\nprintln(length(x))\n# ERROR: Cannot use x: value has been moved\n```\n\nYou see, the `@own` operation has _bound_ the variable `x` with the object `[1, 2, 3]`. The second operation then moves the object to `y`, and flips the `.moved` flag on `x` so it can no longer be used by regular operations.\n\nThe equivalent fixes would respectively be:\n\n```julia\n@clone y = x\n# OR\n@lifetime a begin\n    @ref ~a y = x\n    #= operations on reference =#\nend\n```\n\nNote that BorrowChecker.jl does not prevent you from cheating the system and using `y = x` (_however, the library does try to flag such mistakes by recording symbols used in the macro_). To use this library, you will need to _buy in_ to the system to get the most out of it. But the good news is that you can introduce it in a library gradually:  add `@own`, `@move`, etc., inside a single function, and call `@take!` when passing objects to external functions. And for convenience, a variety of standard library functions will automatically forward operations on the underlying objects.\n\n#### Example: Preventing Thread Races\n\nBorrowChecker.jl helps prevent data races by enforcing borrowing rules.\n\nLet's mock up a simple scenario where two threads modify the same array concurrently:\n\n```julia\ndata = [1, 2, 3]\n\nmodify!(x, i) = (sleep(rand()+0.1); push!(x, i))\n\nt1 = Threads.@spawn modify!(data, 4)\nt2 = Threads.@spawn modify!(data, 5)\n\nfetch(t1); fetch(t2)\n```\n\nThis has a silent race condition, and the result will be non-deterministic.\n\nNow, let's see what happens if we had used BorrowChecker:\n\n```julia\n@own :mut data = [1, 2, 3]\n\nt1 = Threads.@spawn @bc modify!(@mut(data), 4)\nt2 = Threads.@spawn @bc modify!(@mut(data), 5)\n```\n\nNow, when you attempt to fetch the tasks, you will get this error:\n\n```text\nnested task error: Cannot create mutable reference: `data` is already mutably borrowed\n```\n\nThis is because in BorrowChecker.jl's ownership model, similar to Rust, an owned object follows strict borrowing rules to prevent data races and ensure safety.\n(Though, in practice, you should use `BorrowChecker.@spawn` instead of `Threads.@spawn`, so that it validates captured variables.)\n\n### Ownership Rules\n\nAt any given time, an object managed by BorrowChecker.jl can only be accessed in one of the following states:\n\n1. **Direct Ownership:**\n    - The object is accessed directly via its owning variable.\n    - No active references (`Borrowed` or `BorrowedMut`) exist.\n    - In this state, ownership can be transferred (moved) to another variable, after which the original variable becomes inaccessible. The object can also be mutated if it was declared as mutable (`@own :mut ...`).\n\n2. **Immutable Borrows:**\n    - One or more immutable references (`Borrowed`) to the object exist.\n    - While any immutable reference is active:\n        - The original owning variable _cannot_ be mutated directly.\n        - Ownership _cannot_ be moved.\n        - No mutable references (`BorrowedMut`) can be created.\n    - Multiple immutable references can coexist peacefully. This allows multiple parts of the code to read the data concurrently without interference.\n\n3. **A Mutable Borrow:**\n    - Exactly one mutable reference (`BorrowedMut`) to the object exists.\n    - While the mutable reference is active:\n        - The original owning variable _cannot_ be accessed or mutated directly.\n        - Ownership _cannot_ be moved.\n        - No other references (neither immutable `Borrowed` nor other mutable `BorrowedMut`) can be created.\n    - The object _can_ be mutated through the single active mutable reference. This ensures exclusive write access, preventing data races.\n\nIn essence: You can have many readers (`Borrowed`) **or** one writer (`BorrowedMut`), but not both simultaneously. While any borrow is active, the original owner faces restrictions (cannot be moved, cannot be mutated directly if borrowed immutably, cannot be accessed at all if borrowed mutably).\n\n#### Sharp Edges\n\n\u003e [!CAUTION]\n\u003e Be especially careful with closure functions that capture variables, as\n\u003e this is an easy way to silently break the borrowing rules.\n\u003e You should always use the `@cc` macro to wrap closures as a form of\n\u003e validation:\n\u003e\n\u003e ```julia\n\u003e safe_closure = @cc (x, y) -\u003e x + y\n\u003e ```\n\u003e\n\u003e This will validate that any captured variable is an immutable reference.\n\u003e Similarly, you should generally prefer the `BorrowChecker.@spawn` macro instead of\n\u003e `Threads.@spawn` to validate captured variables.\n\n### API\n\n#### Basics\n\n- `@own [:mut] x [= value]`: Create a new owned value (mutable if `:mut` is specified)\n  - These are `Owned{T}` and `OwnedMut{T}` objects, respectively.\n  - You can use `@own [:mut] x` as a shorthand for `@own [:mut] x = x` to create owned values at the start of a function.\n- `@move [:mut] new = old`: Transfer ownership from one variable to another (mutable destination if `:mut` is specified). _Note that this is simply a more explicit version of `@own` for moving values._\n- `@clone [:mut] new = old`: Create a deep copy of a value without moving the source (mutable destination if `:mut` is specified).\n- `@take[!] var`: Unwrap an owned value. Using `@take!` will mark the original as moved, while `@take` will perform a copy.\n- `getproperty` and `getindex` on owned/borrowed values return a `LazyAccessor` that preserves ownership/lifetime until the raw value is used.\n  - For example, for an object `x::Owned{T}`, the accessor `x.a` would return `LazyAccessor{typeof(x.a), T, Val{:a}, Owned{T}}` which has the same reading/writing constraints as the original.\n\n#### References and Lifetimes\n\n- `@lifetime lt begin ... end`: Create a scope for references whose lifetimes `lt` are the duration of the block\n- `@ref ~lt [:mut] var = value`: Create a reference, for the duration of `lt`, to owned value `value` and assign it to `var` (mutable if `:mut` is specified)\n  - These are `Borrowed{T}` and `BorrowedMut{T}` objects, respectively. Use these in the signature of any function you wish to make compatible with references. In the signature you can use `@\u0026(T)` and `@\u0026(:mut, T)` to also allow regular `T`.\n- `Mutex(value)`: Creates a thread-safe container for `value`. Mutexes manage lifetimes implicitly during locks and do not need `@own`.\n- `@ref_into [:mut] var = mutex[]`: Create a reference to the value inside a mutex.\n  - Use `lock(m)` to acquire the lock, `@ref_into` to create a reference to the value inside the mutex, and `unlock(m)` to release the lock.\n- `@bc f(args...; kws...)`: This convenience macro automatically creates a lifetime scope for the duration of the function, and sets up borrowing for any owned input arguments.\n  - Use `@mut(arg)` to mark an input as mutable.\n- `@\u0026 [:mut] T`: Alias for `Union{T, Borrowed[Mut]{T}}` (incl. lazy versions). Use in function signatures to accept `T` or its borrowed form.\n\n#### Validation\n\n- `@cc closure_expr`: Verifies that closures only capture immutable references.\n- `BorrowChecker.@spawn [options...] expr`: A safety wrapper around `Threads.@spawn` that applies `@cc` to the expression (which is internally put inside a closure).\n\n#### Loops\n\n- `@own [:mut] for var in iter`: Create a loop over an iterable, assigning ownership of each element to `var`. The original `iter` is marked as moved.\n- `@ref ~lt [:mut] for var in iter`: Create a loop over an owned iterable, generating references to each element, for the duration of `lt`.\n\n#### Disabling BorrowChecker\n\nYou can disable BorrowChecker.jl's functionality by setting `borrow_checker = false` in your LocalPreferences.toml file (using Preferences.jl). When disabled, all macros like `@own`, `@move`, etc., will simply pass through their arguments without any ownership or borrowing checks.\n\nYou can also set the _default_ behavior from within a module (make sure to do this at the very top, before any BorrowChecker calls!)\n\n```julia\nmodule MyModule\n    using BorrowChecker: disable_by_default!\n\n    disable_by_default!(@__MODULE__)\n    #= Other code =#\nend\n```\n\nThis can then be overridden by the LocalPreferences.toml file.\n\nIf you wanted to use BorrowChecker in a library, the idea is you could disable it by default with this command, but enable it during testing, to flag any problematic memory patterns.\n\n### Further Examples\n\n#### Basic ownership\n\nLet's look at the basic ownership system. When you create an owned value, it's immutable by default:\n\n```julia\n@own x = [1, 2, 3]\npush!(x, 4)  # ERROR: Cannot write to immutable\n```\n\nFor mutable values, use the `:mut` flag:\n\n```julia\n@own :mut data = [1, 2, 3]\npush!(data, 4)  # Works! data is mutable\n```\n\nNote that various functions have been overloaded with the write access settings, such as `push!`, `getindex`, etc.\n\nThe `@own` macro creates an `Owned{T}` or `OwnedMut{T}` object. Most functions will not be written to accept these, so you can use `@take` (copying) or `@take!` (moving) to extract the owned value:\n\n```julia\n# Functions that expect regular Julia types:\npush_twice!(x::Vector{Int}) = (push!(x, 4); push!(x, 5); x)\n\n@own x = [1, 2, 3]\n@own y = push_twice!(@take!(x))  # Moves ownership of x\n\npush!(x, 4)  # ERROR: Cannot use x: value has been moved\n```\n\nHowever, for recursively immutable types (like tuples of integers), `@take!` is smart enough to know that the original can't change, and thus it won't mark a moved:\n\n```julia\n@own point = (1, 2)\nsum1 = write_to_file(@take!(point))  # point is still usable\nsum2 = write_to_file(@take!(point))  # Works again!\n```\n\nThis is the same behavior as in Rust (c.f., the `Copy` trait).\n\nThere is also the `@take(...)` macro which never marks the original as moved,\nand performs a `deepcopy` when needed:\n\n```julia\n@own :mut data = [1, 2, 3]\n@own total = sum_vector(@take(data))  # Creates a copy\npush!(data, 4)  # Original still usable\n```\n\nNote also that for improving safety when using BorrowChecker.jl, the macro will actually store the _symbol_ used.\nThis helps catch mistakes like:\n\n```julia\njulia\u003e @own x = [1, 2, 3];\n\njulia\u003e y = x;  # Unsafe! Should use @clone, @move, or @own\n\njulia\u003e @take(y)\nERROR: Variable `y` holds an object that was reassigned from `x`.\n```\n\nThis won't catch all misuses but it can help prevent some.\n\n#### Lifetimes\n\n\u003cdetails\u003e\n\nReferences let you temporarily _borrow_ values. This is useful for passing values to functions without moving them. These are created within an explicit `@lifetime` block:\n\n```julia\n@own :mut data = [1, 2, 3]\n\n@lifetime lt begin\n    @ref ~lt r = data\n    @ref ~lt r2 = data  # Can create multiple _immutable_ references!\n    @test r == [1, 2, 3]\n\n    # While ref exists, data can't be modified:\n    data[1] = 4 # ERROR: Cannot write original while immutably borrowed\nend\n\n# After lifetime ends, we can modify again!\ndata[1] = 4\n```\n\nJust like in Rust, while you can create multiple _immutable_ references, you can only have one _mutable_ reference at a time:\n\n```julia\n@own :mut data = [1, 2, 3]\n\n@lifetime lt begin\n    @ref ~lt :mut r = data\n    @ref ~lt :mut r2 = data  # ERROR: Cannot create mutable reference: value is already mutably borrowed\n    @ref ~lt r2 = data  # ERROR: Cannot create immutable reference: value is mutably borrowed\n\n    # Can modify via mutable reference:\n    r[1] = 4\nend\n```\n\nWhen you need to pass immutable references of a value to a function, you would modify the signature to accept a `Borrowed{T}` type. This is similar to the `\u0026T` syntax in Rust. And, similarly, `BorrowedMut{T}` is similar to `\u0026mut T`.\n\nDon't worry about references being used _after_ the lifetime ends, because the `lt` variable will be expired!\n\n```julia\njulia\u003e @own x = 1\n       @own :mut cheating = []\n       @lifetime lt begin\n           @ref ~lt r = x\n           push!(cheating, r)\n       end\n       \n\njulia\u003e @show cheating[1]\nERROR: Cannot use r: value's lifetime has expired\n```\n\nThis makes the use of references inside threads safe, because the threads _must_ finish inside the scope of the lifetime.\n\nThough we can't create multiple mutable references, you _are_ allowed to create multiple mutable references to elements of a collection via the `@ref ~lt for` syntax:\n\n```julia\n@own :mut data = [[1], [2], [3]]\n\n@lifetime lt begin\n    @ref ~lt :mut for r in data\n        push!(r, 4)\n    end\nend\n\n@show data  # [[1, 4], [2, 4], [3, 4]]\n```\n\n\u003c/details\u003e\n\n#### Mutating owned values\n\n\u003cdetails\u003e\n\nNote that if you have a mutable owned value,\nyou can use `setproperty!` and `setindex!` as normal:\n\n```julia\nmutable struct A\n    x::Int\nend\n\n@own :mut a = A(0)\nfor _ in 1:10\n    a.x += 1\nend\n# Move it to an immutable:\n@own a_imm = a\n```\n\nAnd, as expected:\n\n```julia\njulia\u003e a_imm.x += 1\nERROR: Cannot write to immutable\n\njulia\u003e a.x += 1\nERROR: Cannot use a: value has been moved\n```\n\n**You should never mutate via variable reassignment.**\nIf needed, you can repeatedly `@own` new objects:\n\n```julia\n@own x = 1\nfor _ in 1:10\n    @own x = x + 1\nend\n```\n\n\u003c/details\u003e\n\n#### Cloning values\n\n\u003cdetails\u003e\n\nSometimes you want to create a completely independent copy of a value.\nWhile you could use `@own new = @take(old)`, the `@clone` macro provides a clearer way to express this intent:\n\n```julia\n@own :mut original = [1, 2, 3]\n@clone copy = original  # Creates an immutable deep copy\n@clone :mut mut_copy = original  # Creates a mutable deep copy\n\npush!(mut_copy, 4)  # Can modify the mutable copy\n@test_throws BorrowRuleError push!(copy, 4)  # Can't modify the immutable copy\npush!(original, 5)  # Original still usable\n\n@test original == [1, 2, 3, 5]\n@test copy == [1, 2, 3]\n@test mut_copy == [1, 2, 3, 4]\n```\n\nAnother macro is `@move`, which is a more explicit version of `@own new = @take!(old)`:\n\n```julia\n@own :mut original = [1, 2, 3]\n@move new = original  # Creates an immutable deep copy\n\n@test_throws MovedError push!(original, 4)\n```\n\nNote that `@own new = old` will also work as a convenience, but `@move` is more explicit and also asserts that the new value is owned.\n\n\u003c/details\u003e\n\n#### Safe use of closures\n\n\u003cdetails\u003e\n\nClosures in BorrowChecker.jl must follow strict rules because they capture variables from their enclosing scope:\n\n```julia\nlet\n    @own x = 42\n    bad_closure = () -\u003e x + 1  # DANGEROUS: captures owned value\nend\n```\n\nThe `@cc` macro validates that closures follow these rules:\n\n```julia\nlet\n    @own x = 42\n\n    # This fails - owned values can't be captured\n    @test_throws ErrorException @cc (a,) -\u003e x + a\n\n    @lifetime lt begin\n        @ref ~lt safe_ref = x  # create an immutable reference\n        \n        # This works - immutable references are safe\n        safe_closure = @cc (a,) -\u003e safe_ref + a\n    end\n    # The reference will expire here, ensuring\n    # the closure doesn't break the borrowing rules!\nend\n```\n\nFor threads, you can use the `BorrowChecker.@spawn` macro instead of the standard `Threads.@spawn`.\nThis ensures safe captures by automatically applying `@cc` to the closure (which is generated internally by `@spawn`):\n\n```julia\n@own x = 42\n@lifetime lt begin\n    @ref ~lt safe_ref = x\n\n    tasks = [\n        BorrowChecker.@spawn safe_ref + 1\n        for _ in 1:10\n    ]\n    sum(fetch, tasks)\nend\n```\n\n\u003c/details\u003e\n\n#### Automated Borrowing with `@bc`\n\n\u003cdetails\u003e\n\nThe `@bc` macro simplifies calls involving owned variables. Instead of manually creating `@lifetime` blocks and references, you just wrap the function call in `@bc`,\nwhich will create a lifetime scope for the duration of the function call,\nand generate references to owned input arguments.\nDeclare which arguments should be mutable with `@mut(...)`.\n\n```julia\n@own config = Dict(\"enabled\" =\u003e true)\n@own :mut data = [1, 2, 3]\n\nfunction process(cfg::@\u0026(Dict), arr::@\u0026(:mut, Vector))\n    push!(arr, cfg[\"enabled\"] ? 4 : -1)\n    return length(arr)\nend\n\n@bc process(config, @mut(data))  # =\u003e 4\n```\n\nUnder the hood, `@bc` wraps the function call in a `@lifetime` block, so references end automatically when the call finishes (and thus lose access to the original object).\n\nThis approach works with multiple positional and keyword arguments, and is a convenient\nway to handle the majority of borrowing patterns. You can freely mix owned, borrowed, and normal Julia values in the same call, and the macro will handle ephemeral references behind the scenes. For cases needing more control or longer lifetimes, manual `@lifetime` usage is a good option.\n\n\u003c/details\u003e\n\n#### Safe multi-threading with `Mutex`\n\n\u003cdetails\u003e\n\nBorrowChecker provides a `Mutex` type analogous to Rust's `Mutex`, for\nthread-safe access to shared data, fully integrated with the\nownership and borrowing system.\n\n```julia\njulia\u003e m = Mutex([1, 2, 3])\n       # ^Regular Julia assignment syntax is fine for Mutexes!\nMutex{Vector{Int64}}([1, 2, 3])\n\njulia\u003e lock(m);\n\njulia\u003e @ref_into :mut data = m[]\n       # ^Mutable reference to the mutex-protected value\nBorrowedMut{Vector{Int64},OwnedMut{Vector{Int64}}}([1, 2, 3], :data)\n\njulia\u003e push!(data, 4);\n\njulia\u003e unlock(m);\n\njulia\u003e m\nMutex{Vector{Int64}}([1, 2, 3, 4])\n```\n\nThe value protected by the mutex is an `OwnedMut` object,\nwhich can therefore be modified.\n\nBecause this value is protected by a spinlock, it is safe to pass\naround with regular Julia assignment syntax. At any point you wish\nto read or write to the value, you can use the `@ref ~m` syntax to\ncreate a reference to the value.\n\nThis reference will automatically expire when the lock is released.\n\n```julia\njulia\u003e m = Mutex(Dict(\"count\" =\u003e 0))\nMutex{Dict{String, Int64}}(Dict(\"count\" =\u003e 0))\n\njulia\u003e @sync for i in 1:100\n           Threads.@spawn begin\n               lock(m) do\n                   @ref_into :mut d = m[]\n                   d[\"count\"] += 1\n               end\n           end\n       end\n\njulia\u003e m\nMutex{Dict{String, Int64}}(Dict(\"count\" =\u003e 100))\n\njulia\u003e d = lock(m) do\n           @ref_into :mut d = m[]\n           d\n       end;\n\njulia\u003e d[\"count\"]  # Try to access the value after the lock is released!\nERROR: Cannot use `d`: value's lifetime has expired\n```\n\n\u003c/details\u003e\n\n#### Introducing BorrowChecker.jl to Your Codebase\n\nWhen introducing BorrowChecker.jl to your codebase, the first thing is to `@own` all variables at the top of a particular function. The simplified version of `@own` is particularly useful in this case:\n\n```julia\nfunction process_data(x, y, z)\n    @own x, y\n    @own :mut z\n\n    #= body =#\nend\n```\n\nThis pattern is useful for generic functions because if you pass an owned variable as either `x`, `y`, or `z`, the original function will get marked as moved.\n\nThe next pattern that is useful is to use `@\u0026 T` and `@\u0026 :mut T` syntax for extending signatures. This is basically equal to `Union{T, Borrowed{T}}` and `Union{T, BorrowedMut{T}}`, respectively (as well as their lazy versions). Let's say you have some function:\n\n```julia\nstruct Bar{T}\n    x::Vector{T}\nend\n\nfunction foo(bar::Bar{T}) where {T}\n    sum(bar.x)\nend\n```\n\nNow, you'd like to modify this so that it can accept _references_ to `Bar` objects from other functions. Since `foo` doesn't need to mutate `bar`, we can modify this as follows:\n\n```julia\nfunction foo(bar::@\u0026(Bar{T})) where {T}\n    sum(bar.x)\nend\n```\n\nThus, the full `process_data` function might be something like:\n\n```julia\nfunction process_data(x, y, z)\n    @own x, y\n    @own :mut z\n\n    @lifetime lt begin\n        @ref ~lt r = z\n        tasks = [\n            BorrowChecker.@spawn(foo(r)),\n            BorrowChecker.@spawn(foo(r)),\n        ]\n        sum(fetch, tasks)\n    end\nend\n```\n\nBecause we modified `foo` to accept `@\u0026 Bar{T}`, we can safely pass immutable references to `z`, and it will _not_ be marked as moved in the original context! Immutable references are safe to pass in a multi-threaded context, so this doubles as a good way to prevent unintended thread races.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMilesCranmer%2FBorrowChecker.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMilesCranmer%2FBorrowChecker.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMilesCranmer%2FBorrowChecker.jl/lists"}