# How to avoid GC of Julia structs within C/C++/Rust data structures?

**URL:** <https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814>\
**Category:** General Usage\
**Created:** [October 2, 2025, 2:57am UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814 "2025-10-02T02:57:25Z")\
**Posts on this page:** 5\
**Page:** 2

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 4, 2025, 10:09pm UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814/21 "2025-10-04T22:09:28Z")

</div>

Here’s an example.

First the julia code to create a foreign `DataType`:

```julia
function maketype(marker, mod::Module=@ __MODULE__ )

    dtp = ccall(:jl_new_foreign_type,
               Any,
               (Symbol,Module,Any,Ptr{Cvoid}, Ptr{Cvoid}, Cint, Cint),
               :QueueRef, mod, Any, marker, C_NULL, 1, 0)
    mod.eval(
        quote
            const QueueRef = $dtp
            # a constructor:
            function QueueRef(h::Ptr)
                # make a new QueueRef object 
                ptls = Core.getptls()
                newobj = ccall(:jl_gc_alloc_typed, Any, 
                               (Ptr{Cvoid}, Csize_t, Any,),
                               ptls, sizeof(Ptr{Cvoid}), QueueRef)::QueueRef
                # store the pointer in it
                # this would be your queue handle
                unsafe_store!(Ptr{UInt}(pointer_from_objref(newobj)), UInt(h), 1)
                # return the object
                newobj
            end
            Base.getindex(qr::QueueRef) = unsafe_load(Ptr{Any}(pointer_from_objref(qr)))
            # here an enqueue, dequeue and offer may be defined for the QueueRef.
        end
    )
end

```

Then some C-code which I compile to a shared library in the file `libmarker.so`. This is where you loop through the C-queue and mark each entry. I only mark the content of the `QueueRef` object, in my case a `Vector{Int}`. Nothing can happen to julia objects during marking, all normal julia threads are waiting during the entire GC, so no need to think about concurrency.

```C
#include <stdio.h>
#include "julia.h"
#include "julia_gcext.h"

int marker(jl_ptls_t ptls, jl_value_t *obj[]) {
  jl_value_t *p = obj[0];
  int marks = jl_gc_mark_queue_obj(ptls, p);
  if(marks > 0) printf("marked %p %d\n",p,marks);
  return marks;
}

```

Then a julia script using this stuff, which shows that I can lose all refs to the vector `v`. A pointer is hidden inside the `QueueRef` object, and the `marker` C-function takes care of marking it as still in use:

```julia
using Libdl
flib = dlopen("./libmarker.so")
cfun = dlsym(flib,:marker)
maketype(cfun)

v = [42, 42, 42]
finalizer(v) do o
    @ccall printf("finalize %p\n"::Cstring; pointer_from_objref(o)::Ptr{Nothing})::Cint
end

a = QueueRef(pointer_from_objref(v));
julia> a
QueueRef()

julia> v = nothing

julia> GC.gc(true)
marked 0x77e04e73f030 1
marked 0x77e04e73f030 1

julia> GC.gc(true)
marked 0x77e04e73f030 1

julia> a = nothing

julia> GC.gc(true)
finalize 0x77e04e73f030

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 6, 2025, 4:10pm UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814/22 "2025-10-06T16:10:15Z")

</div>

> [@alexandergunnarson](#):
>
> @vchuravy so it’s O(n) on the size of the queue on every GC run?

It’s O(n), yes. But you don’t want much gc in a performant parallel program anyway. As long as your wrapper doesn’t allocate a lot, that would be up to the user.

In such a marker you don’t have to worry about concurrency. All julia threads are waiting while gc runs, so nothing can happen to the C-structures while marking.

---

<div class="post-metadata">

**Author:** ![alexandergunnarson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/alexandergunnarson/32/216688_2.png) [@alexandergunnarson](https://discourse.julialang.org/u/alexandergunnarson)\
**Post date:** [October 9, 2025, 3:13pm UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814/23 "2025-10-09T15:13:33Z")

</div>

This is very helpful @sgaure — thank you!

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 9, 2025, 6:01pm UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814/24 "2025-10-09T18:01:05Z")

</div>

Just for the reference, I made a more streamlined interface, where the foreign ref can hold an arbitrary struct with more than a pointer. The struct layout must be synchronized between C and julia, but I think there are packages which can do this (CBinding.jl ?) I had the idea to make a package ForeignRefs for these things, but I don’t have the time to maintain it. Unfortunately, one can’t write the marker function in julia and pass it on as a `@cfunction`, since calling a `@cfunction` accesses some GC structures with a lock, resulting in a deadlock.

> **Definitions**
>
> ```julia-auto
> abstract type ForeignRef{T} end
> 
> function makeforeigntype(name, ::Type{T},
> marker::Ptr{Nothing}, sweeper::Ptr{Nothing},
> mod::Module) where {T}
> 
> isconcretetype(T) || throw(ArgumentError("Must be a concrete type: $T"))
> sname = Symbol(name)
> dt = ccall(:jl_new_foreign_type,
> Any,
> (Symbol,Module,Any,Ptr{Cvoid}, Ptr{Cvoid}, Cint,Cint),
> sname, mod, ForeignRef{T}, marker, sweeper, 1, 0)
> mod.eval(:(const $sname = $dt))
> end
> 
> # constructor for a foreign type
> function (::Type{FT})(obj::T) where {T, FT<:ForeignRef{T}}
> isconcretetype(FT) || (() -> throw(MethodError(FT,Tuple{T})))()
> newobj = ccall(:jl_gc_alloc_typed, Any,
> (Ptr{Cvoid}, Csize_t, Any,),
> Core.getptls(), sizeof(T), FT)::FT
> unsafe_store!(Ptr{T}(pointer_from_objref(newobj)), obj)
> newobj
> end
> 
> contenttype(fr::ForeignRef{T}) where T = T
> Base.getindex(fr::ForeignRef{T}) where T = unsafe_load(Ptr{T}(pointer_from_objref(fr)))
> 
> ```

And a marker, not marking anything, since the example has no reference to julia objects:

> **Marker, just printing the content**
>
> ```julia-auto
> #include <stdio.h>
> #include "julia.h"
> #include "julia_gcext.h"
> 
> int marker(jl_ptls_t ptls, jl_value_t *obj[]) {
> 
> int64_t a = (int64_t) obj[0];
> int64_t b = (int64_t) obj[1];
> uint64_t c = (uint64_t) obj[2];
> printf("marking a=%ld, b=%ld, c=0x%lx\n", a, b, c);
> return 0;
> }
> 
> ```

And the more general interface, where any struct can be held by the foreign type:

```julia-auto
struct MyStruct
    a::Int
    b::Int
    c::Ptr{Nothing}
end

mystruct = MyStruct(17, 14, Ptr{Nothing}(0x42bad00bad42))

using Libdl

flib = dlopen("./libmarker.so")
marker = dlsym(flib,:marker)

makeforeigntype(:FRef, MyStruct, marker, C_NULL, Main)

fref = FRef(mystruct)
mystruct = nothing

julia> dump(fref[]) # mystruct is in there!
MyStruct
  a: Int64 17
  b: Int64 14
  c: Ptr{Nothing}(0x000042bad00bad42)

```

---

<div class="post-metadata">

**Author:** ![alexandergunnarson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/alexandergunnarson/32/216688_2.png) [@alexandergunnarson](https://discourse.julialang.org/u/alexandergunnarson)\
**Post date:** [October 13, 2025, 2:52pm UTC](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814/25 "2025-10-13T14:52:29Z")

</div>

Thanks @sgaure — looks like that could really come in handy. For now using the Julia port of the optimized C queue is way faster than using the moodycamel C++ queue, but I’m sure I’ll return to this once I lean on some other native data structures in the near future.

[Previous page](https://discourse.julialang.org/t/how-to-avoid-gc-of-julia-structs-within-c-c-rust-data-structures/132814.md?page=1)
