# \[Guide\] Using sum types to define dynamic C APIs for Julia 1.12 \`--trim\`

**URL:** https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111
**Category:** General Usage
**Tags:** trim, trimming, static-compilation
**Created:** [September 4, 2025, 10:23pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111 "2025-09-04T22:23:53Z")
**Posts on this page:** 10
**Page:** 2

<div class="post-metadata">

### Author: ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)
#### Post date: [September 8, 2025, 11:41pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/22 "2025-09-08T23:41:54Z")

</div>

I tried to dig deeper and indeed LightSumTypes.jl fails in the general case, sorry for having been wrong, e.g. I think this is a total failure in terms of inference:

```julia-auto
julia> using LightSumTypes

julia> @sumtype X(Bool, Int, Vector{Bool}, Vector{Int})

julia> xs = [X([1,2]), X(true)]

julia> Base.sum(x::X) = sum(variant(x));

julia> @code_warntype sum.(xs)
MethodInstance for (::var"##dotfunction#230#3")(::Vector{X})
  from (::var"##dotfunction#230#3")(x1) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#230#3"())
  x1::Vector{X}
Body::AbstractVector
1 ─ %1 = Base.broadcasted(Main.sum, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(sum), Tuple{Vector{X}}}
│ %2 = Base.materialize(%1)::AbstractVector
└── return %2

```

LightSumTypes.jl works efficiently when working with fields of structs, but I actually didn’t try to work something out for this use case, and I’m afraid that currently it doesn’t work as expected.

Though, I think we can easily patch this with adding to the source code something like:

```julia-auto
function apply(f::Function, sumt)
   v = $LightSumTypes.unwrap(sumt)
   $(branchs(variants, :(return f(v))))
end

```

with this @code\_warntype returns

```julia-auto
julia> Base.sum(x::X) = apply(sum, x);

julia> @code_warntype sum.(xs)
MethodInstance for (::var"##dotfunction#230#1")(::typeof(sum), ::Vector{X})
  from (::var"##dotfunction#230#1")(x1, x2) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#230#1"())
  x1::Core.Const(sum)
  x2::Vector{X}
Body::Vector{Int64}
1 ─ %1 = Base.broadcasted(Main.apply, x1, x2)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(apply), Tuple{Base.RefValue{typeof(sum)}, Vector{X}}}
│ %2 = Base.materialize(%1)::Vector{Int64}
└── return %2

```

I don’t have much time at the moment, but if someone wants to help by implementing a more structured version of this idea, I would be glad to include something like this 🙂

Though it will be harder for functions which require multiple inputs, some of which are not sumtypes. We are basically bound to try to pattern match automatically. Maybe possible with generated functions?

---

<div class="post-metadata">

### Author: ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)
#### Post date: [September 9, 2025, 11:58am UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/23 "2025-09-09T11:58:30Z")

</div>

It also occurred to me yesterday that the code

```julia
sum(m::MyMatrix2{T}) where {T} = sum(variant(m))

```

is **not** type grounded. If we rewrite this function as

```julia
function sum(m::MyMatrix2{T}) where {T}
  mvar = variant(m)
  sum(mvar)
end

```

notably we can not infer the type of `mvar` just from the type `MyMatrix2{T}`, which by definition means this is not type grounded. Still, `JET` still seems fine with this, so I wonder if trimming ends up working or not. I’ll make some MWEs to test trimming.

---

<div class="post-metadata">

### Author: ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)
#### Post date: [September 9, 2025, 1:14pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/24 "2025-09-09T13:14:58Z")

</div>

I can confirm that both versions with Moshi and LightSumTypes are trimmable without any verifier errors or problems at runtime. Check out my example here which includes usage from C run on CI (check the `make` step [here](https://github.com/RomeoV/TrimmableCAPIsViaSumTypes.jl/actions/runs/17583753252/job/49946192470)).

> **[GitHub - RomeoV/TrimmableCAPIsViaSumTypes.jl: https://discourse.julialang.org/t/guide-using-sum-...](https://github.com/RomeoV/TrimmableCAPIsViaSumTypes.jl)**
>
> https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/

Now, how this relates to my post just above I’m not sure. I suppose for these calls the compiler is able to come up with type grounded through lowering or compiler tricks somehow…

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [September 9, 2025, 4:22pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/25 "2025-09-09T16:22:58Z")

</div>

> [@RomeoV](#):
>
> I suppose for these calls the compiler is able to come up with type grounded through lowering or compiler tricks somehow

My guess is that `sum(variant(m))` is optimized with [Union-splitting](https://julialang.org/blog/2018/08/union-splitting/), which branches to statically dispatched calls for very small instabilities (one input with 3 types by default, at least 2 types due to `nothing` semantics). I alluded to it earlier, but try JET on and trim this to see if it Union-splits as well:

```julia-auto
@ccallable function matrixsum_cc(sz::Cint, ptr::Ptr{Cdouble}, matflag::Cint)::Cdouble
    m = build_matrix(sz, ptr, matflag)
    return sum(m)
end

function build_matrix(sz::Integer, ptr::Ptr{Cdouble}, matflag::Cint)
    if matflag == 1
        # Unsafe pointer logic to create the Matrix
        m_dense = unsafe_wrap(Matrix{Cdouble}, ptr, (sz, sz))
        return m_dense
    elseif matflag == 2
        m_diag = Diagonal(unsafe_wrap(Vector{Cdouble}, ptr, sz))
        return m_diag
    else
        error("Matrix (1) or Diagonal (2) only.")
    end
end

```

---

<div class="post-metadata">

### Author: ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)
#### Post date: [September 9, 2025, 4:31pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/26 "2025-09-09T16:31:39Z")

</div>

That’s exactly it in my experiments. With a small number of types the compiler is able to use Union-splitting. Though as I showed above it fails with more types. So we need something like the `apply` trick I mentioned, which would be type-grounded as far as I can tell. Opened this issue [Function to perform branching automatically to solve inference failures · Issue #120 · JuliaDynamics/LightSumTypes.jl · GitHub](https://github.com/JuliaDynamics/LightSumTypes.jl/issues/120) tracking this feature in LightSumTypes.jl based on this discussion

---

<div class="post-metadata">

### Author: ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)
#### Post date: [September 10, 2025, 1:46am UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/27 "2025-09-10T01:46:38Z")

</div>

Attempt for the apply function, seems to work perfectly! [Add apply function for type stable calls of functions by Tortar · Pull Request #121 · JuliaDynamics/LightSumTypes.jl · GitHub](https://github.com/JuliaDynamics/LightSumTypes.jl/pull/121)

I’m actually thinking that LightSumTypes.jl is too opinionated in some of its implementation. I will work as soon as I find the time to its successor adding the most minimalistic interface I can think of in this repo (for now empty): [GitHub - Tortar/WrappedUnions.jl: Wrap a Union and Enjoy Type-Stability](https://github.com/Tortar/WrappedUnions.jl) 🙂

---

<div class="post-metadata">

### Author: ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)
#### Post date: [September 10, 2025, 9:57pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/28 "2025-09-10T21:57:21Z")

</div>

Made it! [WrappedUnions.jl](https://github.com/Tortar/WrappedUnions.jl). If you have any suggestion, please open an issue (maybe I’m clattering too much this thread), I would be glad to discuss! By the way, 70 LOC 😃

---

<div class="post-metadata">

### Author: ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)
#### Post date: [October 11, 2025, 6:01am UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/29 "2025-10-11T06:01:34Z")

</div>

> [@Tortar](#):
>
> I tried to dig deeper and indeed [LightSumTypes.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/LightSumTypes) fails in the general case

I can’t reproduce this, as I get `Vector{Int64}` both with Julia 1.11.7 and Julia 1.12.0 using `LightSumTypes.jl` `v5.2.2`. Maybe something has been backported to fix this?

I wondered whether a `Union` with `Base.Experimental.@max_methods` would fix the problem, but without a problem that’s hard to tell as both versions return `Vector{Int64}`:

```julia
struct A1 i::Int end
struct A2 i::Int end
struct A3 i::Int end
struct A4 i::Int end
struct A5 i::Int end
const A = Union{A1, A2, A3, A4, A5}
const as = A[A1(1), A2(2), A3(3), A4(4), A5(5)]

extract(a::A) = a.i
@code_warntype extract.(as)

Base.Experimental.@max_methods 5 function maxextract end
maxextract(a::A) = a.i
@code_warntype maxextract.(as)

```

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 11, 2025, 10:12am UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/30 "2025-10-11T10:12:27Z")

</div>

> [@PatrickHaecker](#):
>
> `@code_warntype extract.(as)`

The inference of an output type for broadcasting is related but a bit different from the compiler inferring types of expressions. To see the latter, we can just use `extract2(arr, j) = arr[j].i`. `@code_warntype(as, 1)` infers `::A` for the indexing, but the field is still inferred as `::Int`. Union-splitting’s limit was lifted for unions in one position, apparently, so it’ll branch if it needs to. In this case, it doesn’t because every branch would do the same thing.

---

<div class="post-metadata">

### Author: ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)
#### Post date: [October 11, 2025, 12:07pm UTC](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111/31 "2025-10-11T12:07:14Z")

</div>

Field access has been optimized appropriately in LightSumTypes.jl because it branches as it should. In the general case it doesn’t so it fails. When I said that it failed I was referring to this code in particular:

> ```julia-auto
> julia> using LightSumTypes
> 
> julia> @sumtype X(Bool, Int, Vector{Bool}, Vector{Int})
> 
> julia> xs = [X([1,2]), X(true)]
> 
> julia> Base.sum(x::X) = sum(variant(x));
> 
> julia> @code_warntype sum.(xs)
> MethodInstance for (::var"##dotfunction#230#3")(::Vector{X})
> from (::var"##dotfunction#230#3")(x1) @ Main none:0
> Arguments
> #self#::Core.Const(var"##dotfunction#230#3"())
> x1::Vector{X}
> Body::AbstractVector
> 1 ─ %1 = Base.broadcasted(Main.sum, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(sum), Tuple{Vector{X}}}
> │ %2 = Base.materialize(%1)::AbstractVector
> └── return %2
> 
> ```

WrappedUnions.jl allows one to control much better union-splitting behaviour with the `@unionsplit` macro instead.

[Previous page](https://discourse.julialang.org/t/guide-using-sum-types-to-define-dynamic-c-apis-for-julia-1-12-trim/132111.md?page=1)
