StaticCompiler.jl: turning a Julia function into a native library
Compiles Julia code to a standalone library (experimental)
At a glance
- What is it?
- StaticCompiler.jl is an experimental Julia package that compiles a function into a native executable or shared library with no system image and no libjulia, using GPUCompiler for code generation. The trade is a long list of restrictions, from no heap allocation to no type instability, and the package is most useful to people writing code that has to be called from another language.
- Who is it for?
- StaticCompiler.jl fits a Julia developer whose numeric kernel has to be called from C, Python or another host, and who is prepared to write that kernel type stable and allocation free.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Julia, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the package actually produces
StaticCompiler.jl is described as an experimental package to compile Julia code to standalone libraries, and the sentence that matters is the second one: a system image is not needed. That is the whole difference between it and the ordinary way of shipping Julia code. A normal Julia deployment ships a system image, which is a large serialised snapshot of the compiled standard library, and the host process loads it at startup. This package produces a native executable or a shared library that does not need any of that.
The two entry points are `compile_executable` and `compile_shlib`, and they take a Julia function plus its argument types and produce a native executable or a shared library for use from outside of Julia. The example in the README shows the whole flow.
using Pkg
Pkg.add("StaticCompiler")That is the installation, and it is the same as any other registered Julia package. The usage is a one-liner at the REPL.
julia> using StaticCompiler, StaticTools
julia> hello() = println(c"Hello, world!")
hello (generic function with 1 method)
julia> compile_executable(hello, (), "./")
"/Users/user/hello"The interesting detail in that snippet is the `c"Hello, world!"` string literal. The c prefix is what makes it a static string, and it comes from StaticTools, which the README links as the place to look for ways to work around the limitations. The compile call returns the path of the artefact, and the README shows the result being listed and run from a shell, producing an 8.4K binary that prints the greeting.
An 8.4K executable that prints a string is a small but real demonstration: there is no Julia runtime in that file. The README is direct about the consequence, that this approach comes with substantial limitations compared to regular julia code, as you cannot rely on julia's runtime, `libjulia`.
For finer control there is a lower-level function, `StaticCompiler.generate_obj`, which is not exported and generates object files. The README says this can be used for more control of compilation, and for example to cross-compile to other targets.
Method overlays, and the error handling problem
The hard part of compiling Julia statically is that the language is full of operations that assume a garbage collector and a runtime. StaticCompiler's answer to that is a macro, `@device_override`, which lets you swap out a method, but only inside of a StaticCompiler.jl compilation context.
The example in the README is small and shows the mechanism precisely.
julia> using Libdl, StaticCompiler
julia> f(x) = g(x) + 1;
julia> g(x) = 2x
julia> @device_override g(x::Int) = x - 10
julia> f(1) # Gives the expected answer in regular julia
3The last few lines load the compiled shared library, look up the symbol with `dlsym`, and call it through `@ccall`, and the answer changes from 3 to -8 because the override replaced g with x minus 10 inside the compiled artefact only. The regular Julia call is unaffected.
That is the escape hatch, and the README says it is what to use when a function you want to compile does things such as throwing errors that are not supported natively by StaticCompiler.
The recommended pattern for errors is to override them with `@print_and_throw`, which is described as StaticCompiler friendly.
@device_override @noinline Base.Math.throw_complex_domainerror(f::Symbol, x) =
@print_and_throw c"This operation requires a complex input to return a complex result"The limitation section states the rule behind this: since error handling relies on libjulia, you can only throw errors from standalone-compiled code if an explicit overload has been defined for that particular error with `@device_override`, with a pointer to quirks.jl in the source tree where those overrides live.
The same mechanism also accepts a different method table. If you want to use a method table defined with `Base.Experimental.@MethodTable` and `Base.Experimental.@overlay` rather than the default one provided by StaticCompiler, you pass it to `compile_executable` and `compile_shlib` through a `method_table` keyword argument.
The four limitations that shape everything else
The limitations section in the README is short and it is the part to read before writing any code.
GC-tracked allocations and global variables do not work with `compile_executable` or `compile_shlib`. The README spells out the consequence, which is the single most important sentence in the documentation: all functions within the function you want to compile must either be inlined or return only native types, otherwise Julia would have to allocate a place to put the results, which will fail.
That is the constraint everything else follows from. There is no heap, so there is nowhere to put a boxed value, so every value crossing a call boundary has to be a native type that lives in a register or on the stack.
The second limitation is the error handling one already covered, that throwing depends on libjulia and therefore needs an explicit override per error type.
The third is type instability. Type unstable code cannot currently be statically compiled via this package. In a language where inference is the default, this is the restriction that will bite a general purpose codebase hardest, because most Julia code that has not been written for this purpose is not fully inferred.
The fourth is platform support. Windows support is extra experimental, with pull requests welcome if you encounter issues, and it should work in WSL on Windows 10 and later. Linux and macOS are the platforms the package is developed against, and the CI badges at the top of the README show separate workflows for the standard build, an integration build, and nightly variants of both, so the package is tested against Julia's nightly releases as well as the stable ones.
Writing code that survives static compilation
The guide for package authors is the most practically useful part of the README, because it describes how to write Julia that this compiler can handle.
Use type-stable code. Use Tuples, NamedTuples, StaticArrays and other types where appropriate, since these allocate on the stack and do not use Julia's heap allocation. Avoid Julia's internal allocations, which in practice means do not bake in use of Arrays or Strings or Dicts, and the types from StaticTools can help, such as StaticStrings and MallocArrays.
If memory must be managed by hand, the README points at `malloc` and `free` from StaticTools, which works with `StaticTools.MallocString` and `StaticTools.MallocArray`, or with Bumper.jl.
Do not use global variables that need to be allocated and initialised. The recommended replacement is context structures with an initialisation function, and the README is careful to add that it is okay to use global Tuples or NamedTuples, since their use should be baked into the compiled code.
Context variables are also how program state, inputs and outputs are stored, and they should be parameterised so the code can handle normal types such as Arrays and static-friendly types such as StaticArrays, MallocArrays or StrideArrays. The README names the SciML ecosystem as doing this well and links to an OrdinaryDiffEq integrator type file as the example. Using context variables also enables allocations and initialisation to be centralised, so these can be managed by the calling routines in Julia, Python, JavaScript or another language, which is the point when the compiled library is called from a host that has a heap and Julia does not.
The interface rules are narrower still. Arguments and returned values from `compile_shlib` must be native objects such as `Int`, `Float64` or `Ptr`, and they cannot be things like `Tuple{Int, Int}` because that is not natively sized, so such objects need to be passed by reference instead of by value. If the code needs an array as a workspace, the fix is to create it as a function argument rather than directly, where it could default to a standard array creation, so the code could be statically compiled if that argument is changed to a MallocArray or another static-friendly alternative.
What the package is for, and who should not use it
The guide for statically compiling code addresses the case where the code you are calling is generic and uses features StaticCompiler does not support. The options it gives are to change the code you are calling using the tips above, or to compile it anyway, using method overlays to change what methods are called. The README points at Cthulhu as the tool for that work, and the text ends there.
That is the honest picture of the package's scope. It is not a way to ship an arbitrary Julia application. It is a way to extract a numeric kernel, compile it, and call it from somewhere else, at the cost of rewriting that kernel so that it has no heap, no globals and no type instability.
The obvious use case is a hot inner loop called from a larger program in another language, where the cost of a Julia process with a system image is unacceptable and the kernel is small enough to be made static. The second use case is embedding, where a shared library is loaded by a host application through `dlopen` and `dlsym` as the README's overlay example does, and the library is expected to be a leaf that does not call back into Julia.
It is a poor fit for a general codebase, and the reason is worth stating plainly. Julia's standard library is full of allocation, and the parts of it you would want to use, the collections, the string handling, the I/O, all allocate. The workaround for each of them is an override, and the README's own example overrides a specific Base error function. A codebase that needs a dozen overrides is a codebase whose behaviour now depends on a set of hand-written method replacements, and that is a maintenance burden the package does not remove.
The alternative worth weighing is Julia's normal packaging, where a system image and libjulia are available and any Julia code works, with the cost being a multi-hundred-megabyte dependency and a startup cost. The difference in approach is total rather than incremental: StaticCompiler buys a small artefact and no runtime by requiring the code to be written for a machine without a garbage collector, and PackageCompiler buys a working artefact for any Julia code by shipping the runtime with it. For a library with a hot loop that a C host calls millions of times, the first is worth its constraints. For anything else, the second is the honest recommendation.
Editorial conclusion
StaticCompiler.jl fits a Julia developer whose numeric kernel has to be called from C, Python or another host, and who is prepared to write that kernel type stable and allocation free. It does not fit a developer who wants to reuse ordinary Julia code, since GC-tracked allocations, global variables and type instability are all excluded, and it does not fit a team that needs error handling to work in the compiled artifact, because throwing an error depends on libjulia and needs an explicit override for each error type. Before you start, read the limitations list against the code you intend to compile, and check that the package author you are considering is targeting a version of Julia whose method table matches what StaticCompiler expects.
Frequently asked questions
What is StaticCompiler.jl?
It is an experimental Julia package that compiles Julia code to standalone libraries, and a system image is not needed. It provides compile_executable and compile_shlib for turning a Julia function into a native executable or shared library usable from outside Julia, and it uses the GPUCompiler package to generate code.
How do I install StaticCompiler.jl?
The same as any other registered Julia package, by using Pkg and calling Pkg.add for StaticCompiler. The package author is tshort, the default branch is master, and the most recent release is v0.7.4 from 2026-10-12.
What are the main limitations of StaticCompiler.jl?
GC-tracked allocations and global variables do not work, so every function inside the compiled function must be inlined or return only native types. Type unstable code cannot be compiled, errors can only be thrown where an explicit @device_override has been defined, and Windows support is extra experimental and should work in WSL on Windows 10 and later.
What does the @device_override macro do?
It lets you swap out a method inside a StaticCompiler.jl compilation context, so the compiled artefact calls the replacement while regular Julia code is unaffected. It is the mechanism for handling operations StaticCompiler does not support natively, and errors are typically overridden this way with @print_and_throw.
What types can be passed to a compiled shared library?
Arguments and returned values must be native objects such as Int, Float64 or Ptr, and they cannot be types like Tuple{Int, Int} because those are not natively sized, so such objects must be passed by reference. The README advises using Tuples, NamedTuples and StaticArrays inside the compiled code, and context variables for program state, inputs and outputs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tshort-staticcompiler-jl)Was this useful?