{"id":16730521,"url":"https://github.com/c42f/resourcecontexts.jl","last_synced_at":"2025-10-26T01:32:24.095Z","repository":{"id":45013432,"uuid":"366408281","full_name":"c42f/ResourceContexts.jl","owner":"c42f","description":"Safe resource handling for Julia, without the do blocks","archived":false,"fork":false,"pushed_at":"2022-08-09T13:37:40.000Z","size":79,"stargazers_count":31,"open_issues_count":6,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-10-26T01:32:04.963Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c42f.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-11T14:21:48.000Z","updated_at":"2025-06-10T06:04:41.000Z","dependencies_parsed_at":"2022-09-24T21:31:40.949Z","dependency_job_id":null,"html_url":"https://github.com/c42f/ResourceContexts.jl","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/c42f/ResourceContexts.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2FResourceContexts.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2FResourceContexts.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2FResourceContexts.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2FResourceContexts.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c42f","download_url":"https://codeload.github.com/c42f/ResourceContexts.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c42f%2FResourceContexts.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281047793,"owners_count":26435124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-25T02:00:06.499Z","response_time":81,"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":[],"created_at":"2024-10-12T23:33:47.780Z","updated_at":"2025-10-26T01:32:24.061Z","avatar_url":"https://github.com/c42f.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ResourceContexts.jl\n\n[![Build Status](https://github.com/c42f/ResourceContexts.jl/workflows/CI/badge.svg)](https://github.com/c42f/ResourceContexts.jl/actions)\n\n`ResourceContexts` is an experimental Julia package for **composable resource\nmanagement** without `do` blocks.\n\nResources are objects which need cleanup code to run to finalize their state.\nFor example,\n* Open file handles\n* Temporary files and directories\n* Background `Task`s\n* Many other things which are currently handled with `do`-blocks.\n\nThe `@!` macro calls or defines \"context functions\" — functions which take an\n`AbstractContext` as the first argument and associate any resources with that\ncontext. This package provides context-based overrides for `Base` functions\n`open`, `mktemp`, `mktempdir`, `cd`, `run`, `lock` and\n`redirect_{stdout,stderr,stdin}`.\n\nThe `@context` macro associates a context with the current code block which\nwill be passed to any context functions invoked with `@!`. When a `@context`\nblock exits the cleanup code associated with the context runs.\n\nThe `@defer` macro defers an arbitrary cleanup expression to the end of the\ncurrent `@context`.\n\n## Examples\n\n### Manging resources without do blocks\n\nOpen a file, read all the lines and close it again\n\n```julia\nfunction f()\n    @context readlines(@!(open(\"tmp/hi.txt\", \"r\")))\nend\n```\n\nCreate a temporary file and ensure it's cleaned up afterward\n\n```julia\n@context function f()\n    path, io = @! mktemp()\n    write(io, \"content\")\n    flush(io)\n    @info \"mktemp output\" path ispath(path) isopen(io) read(path, String)\nend\n```\n\nAcquire a pair of locks (and release them in the opposite order)\n\n```julia\nfunction f()\n    lk1 = ReentrantLock()\n    lk2 = ReentrantLock()\n    @context begin\n        @! lock(lk1)\n        @! lock(lk2)\n        @info \"Hi from locked section\" islocked(lk1) islocked(lk2)\n    end\n    @info \"Outside locked section\" islocked(lk1) islocked(lk2)\nend\n```\n\nStart ten external processes and wait for all of them to finish before\ncontinuing\n\n```julia\n@context begin\n    for i=1:10\n        @! run(`sleep $(rand(2))`)\n    end\nend\n```\n\n### Functions which pass resources back to their callers\n\nFunctions called as `@! foo(args...)` are passed the current context in the\nfirst argument; `foo(current_context, args...)` is called.  When `foo` is\n*defined* using `@!`, the context will automatically defer resource cleanup to\nthe caller when using `@defer`. For example:\n\nReturning a bare `Ptr` to a temporary buffer:\n\n```julia\n@! function raw_buffer(len)\n    buf = Vector{UInt8}(undef, len)\n    @defer GC.@preserve buf nothing\n    pointer(buf)\nend\n\n@context begin\n    len = 1_000_000_000\n    ptr = @! raw_buffer(len)\n    GC.gc() # `buf` is preserved regardless of this call to gc()\n    unsafe_store!(ptr, 0xff)\nend\n```\n\nDefer zeroing of a secret buffer to the caller\n\n```julia\n@! function create_secret()\n    buf = Base.SecretBuffer()\n    write(buf, rand(UInt64)) # super secret ?\n    seek(buf, 0)\n    @defer Base.shred!(buf)\n    buf\nend\n\n@context begin\n    buf = @! create_secret()\n    @info \"Secret first byte\" read(buf, 1)\nend\n# buf has been `shred!`ed at this point\n```\n\n### Interop with \"do-block-based\" resource management\n\nThis is available with the `enter_do` function, which can \"steal\" the state\nfrom inside the do block and make it available in a `@context` block, or in the\nREPL:\n\n```julia\nfunction resource_func(f::Function, arg)\n    @info \"Setting up resources\"\n    fake_resource = 40\n    f(fake_resource + arg)\n    @info \"Tear down resources\"\nend\n\n# Normal usage\nresource_func(2) do x\n    @info \"Resource ready\" x\nend\n\n# Safely access the resource in the REPL\nx = @! enter_do(resource_func, 2)\n```\n\n### Interop with finalizer-based resource management\n\nThe special function `@! detach_context_cleanup(x)` can be used to detach\ncontext cleanup from the current `@context` block and associate it with the\nfinalization of `x` instead. That is, it turns *lexical* resource management\ninto *dynamic* resource management.\n\nFor example, to create a temporary directory with two files in it, return\nthe directory name as a string and only clean up the directory when `dir` is\nfinalized:\n\n```\ndir = @context begin\n    dir = @! mktempdir()\n    write(joinpath(dir, \"file1.txt\"), \"Some content\")\n    write(joinpath(dir, \"file2.txt\"), \"Some other content\")\n    @! ResourceContexts.detach_context_cleanup(dir)\nend\n```\n\n# Design\n\nThe standard solution for Julian resource management is still the `do` block,\nbut this has some severe ergonomic disadvantages:\n* It's extremely inconvenient at the REPL; you cannot work with the\n  intermediate open resources without entering the context of the `do` block.\n* It creates highly nested code when many resources are present. This is both\n  visually confusing and the excess nesting leads to very deep stack traces.\n* Custom cleanup code is separated from the resource creation in a `finally`\n  block.\n\nThe ergonomic factors mean that people often prefer the non-scoped form as\nargued [here](https://github.com/JuliaLang/julia/issues/7721#issuecomment-171345256).\nHowever this also suffers some severe disadantages:\n* Resources leak (or must be finalized by the GC) when people forget to guard\n  resource cleanup with a `try ... finally`.\n* Finalizers run in a restricted environment where any errors occur outside the\n  original context where the resource was created. This makes for *unstructured\n  error handling* where it's impossible to propagate errors in a natural way.\n* Functions which return objects must keep the backing resources alive by\n  holding references to them somewhere. There's two ways to do this:\n  - Have the returned object hold a reference to each resource. This is bad\n    for the implementer because it reduces composability: one cannot combine\n    any desired return type with arbitrary backing resources.\n  - Have multiple returns such as `(object,resource)`. This is unnatural\n    because it forces the user to unpack return values.\n\n## The solution\n\nThis package uses the macro `@!` as a proxy for the [proposed postfix `!`\nsyntax](https://github.com/JuliaLang/julia/issues/7721#issuecomment-170942938)\nand adds some new ideas:\n\n**The user should not be able to \"forget the `!`\"**. We prevent this by\nintroducing a new *context calling convention* for resource creation functions\nwhere the current `AbstractContext` is passed as the first argument. The\n`@context` macro creates a new context in lexical scope and the `@!` macro is\nsyntactic sugar for calling with the current context.\n\n**Resource creation functions should be able to *compose* any desired object\nreturn type with arbitrary resources**. This preserves the [composability of\nthe `do` block form](https://github.com/JuliaLang/julia/issues/7721#issuecomment-719152859)\nby rejecting the conflation of the returned object and its backing resources.\nThis is a break with some familiar APIs such as the standard file handles\nreturned by `open(filename)` which are both a stream interface and a resource\nin need of cleanup.\n\n## Possible language integration\n\nWhat would all this look like as a language feature?\n\n* `@!` could be replaced with a postfix `!` as proposed way back in 2015 or so.\n* `defer` might become a keyword so that it can have special behavior such as\n  ignoring its return value. In a similar way to the code which runs inside\n  `finally`, there's no sense in having a \"value returned by\" `defer`. In\n  particular, I've observed that it frequently leads to the introduction of\n  temporary variables simply to transfer the result of the expression occurring\n  prior to the `defer` line.\n* `@context` would be implicit at function boundaries, global `let` blocks, and\n  potentially other scopes within functions. Getting this part correct is\n  still a tricky design problem. For example, looping constructs should\n  introduce an implicit context, but how then can the user disable this in\n  particular cases?\n\nUsing the example from above, we've got\n\n```julia\nfunction create_secret()!\n    buf = Base.SecretBuffer()\n    write(buf, rand(UInt64)) # super secret ?\n    seek(buf, 0)\n    defer Base.shred!(buf)\nend\n\nlet\n    buf = create_secret()!\n    @info \"Secret first byte\" read(buf, 1)\nend # \u003c- `buf` shredded here\n```\n\nOne might be concerned that this definition of `create_secret()` hides the\ncalling convention and that explicitly annotating the passed context might be\nmore transparent. In that case we could go with syntax more like the existing\nmacro annotations such as `@nospecialize` which attach metadata to function\narguments. For example,\n\n```julia\nfunction create_secret(@passcontext(ctx::AbstractContext))\n    buf = Base.SecretBuffer()\n    write(buf, rand(UInt64)) # super secret ?\n    seek(buf, 0)\n    defer Base.shred!(buf)\nend\n```\n\n## References\n\n* Resource cleanup with `defer` and `!` syntax\n  - [The postfix `!` syntax](https://github.com/JuliaLang/julia/issues/7721#issuecomment-170942938)\n  - [`close` as a possible default for cleanup](https://github.com/JuliaLang/julia/issues/7721#issuecomment-171004109)\n  - [The `@!` macro proxy syntax for `!`](https://github.com/JuliaLang/julia/issues/7721#issuecomment-277142281)\n* The benefits and drawbacks of `do` syntax\n  - [The ergonomic problems of `do`](https://github.com/JuliaLang/julia/issues/7721#issuecomment-171345256)\n  - [Some composability benefits of `do`](https://github.com/JuliaLang/julia/issues/7721#issuecomment-719152859)\n* Finalizers were discussed at length in https://github.com/JuliaLang/julia/issues/11207\n* A previous prototype, [Defer.jl](https://github.com/adambrewster/Defer.jl)\n  used similar macro-based syntax.\n* Structured concurrency and the cancellation problem is closely related\n  https://github.com/JuliaLang/julia/issues/33248 when viewing `@async` tasks\n  as a type of resource and the task nursury as the context.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc42f%2Fresourcecontexts.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc42f%2Fresourcecontexts.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc42f%2Fresourcecontexts.jl/lists"}