# Wrapping Fortran for Python and Julia

**URL:** https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493
**Category:** Uncategorized
**Created:** [November 15, 2025, 10:28am UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493 "2025-11-15T10:28:01Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![HugoMVale](https://avatars.discourse-cdn.com/v4/letter/h/e19adc/32.png) [@HugoMVale](https://fortran-lang.discourse.group/u/HugoMVale)
#### Post date: [November 15, 2025, 10:28am UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493/1 "2025-11-15T10:28:01Z")

</div>

A while ago, I set myself the challenge of creating Python bindings for a Fortran package. The main goal was to _learn_ how to do it, but I ended up having to learn far more than I had initially planned.

Once that was done, I decided I should also learn how to make Julia bindings. Again, I was forced to dig deeper than expected — but in a good way. It made me revisit and improve the Python bindings.

Along the way, I realized there aren’t many projects out there that you can straightforwardly copy from. I hope these projects can be a small contribution toward filling that gap:

- Fortran source: [odrpack](https://github.com/HugoMVale/odrpack95/)
- Python bindings: [odrpack-python](https://github.com/HugoMVale/odrpack-python/)
- Julia bindings: [Odrpack.jl](https://github.com/HugoMVale/Odrpack.jl/)

The order of steps for creating bindings is slightly different between Python and Julia.

**Python bindings**

1. Create a C API for the functions you want to wrap, along with the corresponding header files.
2. Implement the bindings using a suitable framework (ctypes, cffi, pybind11, nanobind, etc.).
3. Build wheels for all (or most) OS and architecture combinations.
4. Publish to PyPI.

**Julia bindings**

1. Create a C API for the functions you want to wrap; header files are optional.
2. Create an `X_jll` package (a Julia “binary wrapper”) for all (or most) OS and architecture combinations.
3. Write the bindings in pure Julia, invoking the `X_jll` package.
4. Publish to the Julia General Registry.

The first step (creating the C API) is essentially the same for both Python and Julia. In absolute terms it’s not complicated, but there are many small details (skill issues included…) that can eat up a lot of time. **More than once I found myself wishing that the [Fortran Best Practices](https://fortran-lang.org/learn/best_practices/) guide had a section on this topic**; in my opinion, it’s something best learned by studying well-crafted examples.

When it comes to writing the bindings themselves, the experience in Python and Julia differs greatly. In Julia, there is essentially one standard way to do it: no ambiguity. In contrast, the multitude of Python binding frameworks is dizzying — and to make matters worse, writing the bindings and building the wheels must be considered together. It’s easy to end up with something that works perfectly on a given OS/architecture in your local development setup but refuses to run inside the (cloud) wheel builders. This is the main reason I initially chose `pybind11` (and later switched to `nanobind`): I was confident they could be made to work in wheel builders.

**⚠ Callbacks and Closures**

What I struggled with most were callbacks and closures. Wrapping a function that takes another function as an argument requires special care:

- **The Fortran code cannot contain closures involving the callback.**  
With GCC, this results in a trampoline (executable stack), which causes all sorts of trouble. (No, special trampoline flags will not help.) Binaries (wheels or `X_jll`) cannot be generated for musllinux. Even worse, in Julia you get a stack overflow error when the code is executed from the REPL — but not when run from a “new process.” (It’s as crazy as it sounds.)

- **The Julia code cannot have closures on the C callback that will be passed into the Fortran library** , because Julia’s `cFunction` does not support closures on macOS + ARM.

---

<div class="post-metadata">

### Author: ![ivanpribec](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/ivanpribec/32/3290_2.png) [@ivanpribec](https://fortran-lang.discourse.group/u/ivanpribec)
#### Post date: [November 15, 2025, 12:17pm UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493/2 "2025-11-15T12:17:28Z")

</div>

Very interesting topic. Thanks for sharing your experience. I also find the options in Python overwhelming. I also am not satisfied with the auto-wrappers like f2py which can be confusing to use or are tailored to a particular Fortran processor.

When you say “callbacks and closures”, are you referring to the Python and Julia side? Fortran doesn’t have closures in the full sense of the word.

Concerning trampoline flags, are you are referring to `-ftrampoline-impl`, discussed previously here: [Implementation of a parametrized objective function without using module variables or internal subroutines - #41 by ivanpribec](https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines/9919/41) ?

---

<div class="post-metadata">

### Author: ![HugoMVale](https://avatars.discourse-cdn.com/v4/letter/h/e19adc/32.png) [@HugoMVale](https://fortran-lang.discourse.group/u/HugoMVale)
#### Post date: [November 15, 2025, 2:33pm UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493/3 "2025-11-15T14:33:28Z")

</div>

> [@ivanpribec](#):
>
> When you say “callbacks and closures”, are you referring to the Python and Julia side? Fortran doesn’t have closures in the full sense of the word.

Closures (or nested function calls; I don’t know what else to call them) are problematic **both on the Fortran side and on the Julia side** , quite likely for the same reason – trampolines.

Specifically, what got me in trouble on the Fortran side was the pattern shown below. This is perfectly valid Fortran code, but leads to the issues I mentioned.

```fortran
subroutine solver_c(callback_c, ...) bind(c)
  procedure(callback_c_t) :: callback_c

  call solver(callback_f, ...)

  contains

    subroutine callback_f(x, y)
      real, intent(in) :: x(:)
      real, intent(out):: y(:)
      call callback_c(n, x, y)
    end subroutine 

end subroutine

```

> [@ivanpribec](#):
>
> Concerning trampoline flags, are you are referring to `-ftrampoline-impl`, discussed previously here: [Implementation of a parametrized objective function without using module variables or internal subroutines - #41 by ivanpribec](https://fortran-lang.discourse.group/t/implementation-of-a-parametrized-objective-function-without-using-module-variables-or-internal-subroutines/9919/41) ?

Yes, I tried `-ftrampoline-impl=heap` and the like. I don’t mean the flags do not do what they are supposed to. The difficulty is that one needs a solution that works across all OS+arch, and is compatible with the specific constraints of the cloud wheel builders.

---

<div class="post-metadata">

### Author: ![jorgeg](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/jorgeg/32/6835_2.png) [@jorgeg](https://fortran-lang.discourse.group/u/jorgeg)
#### Post date: [November 15, 2025, 9:57pm UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493/4 "2025-11-15T21:57:40Z")

</div>

> [@HugoMVale](#):
>
> The first step (creating the C API) is essentially the same for both Python and Julia. In absolute terms it’s not complicated, but there are many small details (skill issues included…) that can eat up a lot of time. **More than once I found myself wishing that the [Fortran Best Practices](https://fortran-lang.org/learn/best_practices/) guide had a section on this topic**; in my opinion, it’s something best learned by studying well-crafted examples.

We should 100% do it

---

<div class="post-metadata">

### Author: ![mskocic](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/mskocic/32/3378_2.png) [@mskocic](https://fortran-lang.discourse.group/u/mskocic)
#### Post date: [November 16, 2025, 12:56am UTC](https://fortran-lang.discourse.group/t/wrapping-fortran-for-python-and-julia/10493/5 "2025-11-16T00:56:18Z")

</div>

From my experience, the best solution for Python wrappers is to write manually the C extensions. This way you know exactly what you are doing. However, you have to get familiar with the Python C API and it can be time consuming.

As already mentioned the automated such as f2py or Cython are overwhelming.
