# \[ANN\] RustFFT.jl: Compute forward and inverse FFTs with RustFFT

**URL:** <https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574>\
**Category:** Package Announcements\
**Created:** [May 29, 2023, 6:52pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574 "2023-05-29T18:52:51Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Taaitaaiger](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/taaitaaiger/32/49792_2.png) [@Taaitaaiger](https://discourse.julialang.org/u/Taaitaaiger)\
**Post date:** [May 29, 2023, 6:52pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/1 "2023-05-29T18:52:51Z")

</div>

RustFFT is a high-performance, SIMD-accelerated FFT library written in pure Rust. It can compute FFTs of any size, including prime-number sizes, in O(nlogn) time. You can now use it from Julia!

## Usage

Forward FFT:

```julia
using RustFFT

planner64 = RustFFT.FftPlanner64()
instance = RustFFT.plan_fft_forward(planner64, UInt(1))
data = complex([1.0])
RustFFT.fft!(instance, data)
@assert data[1] ≈ 1.0

```

Inverse FFT:

```julia
using RustFFT

planner64 = RustFFT.FftPlanner64()
instance = RustFFT.plan_fft_inverse(planner64, UInt(1))
data = complex([1.0])
RustFFT.fft!(instance, data)
@assert data[1] ≈ 1.0

```

Note that RustFFT does not normalize outputs:

> Callers must manually normalize the results by scaling each element by `1/len().sqrt()`. Multiple normalization steps can be merged into one via pairwise multiplication, so when doing a forward FFT followed by an inverse callers can normalize once by scaling each element by `1/len()`

A few other limitations apply. It’s currently not possible to choose the specific algorithm that will be used to compute the transform. It’s also not possible to compute the FFT of an array with a rank not equal to 1. The interface provided by `AbstractFFTs` is not used yet, either.

[Documentation](https://taaitaaiger.github.io/RustFFT.jl/dev/)  
[GitHub](https://github.com/Taaitaaiger/RustFFT.jl)

---

<div class="post-metadata">

**Author:** ![RoyiAvital](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/royiavital/32/571_2.png) [@RoyiAvital](https://discourse.julialang.org/u/RoyiAvital)\
**Post date:** [May 29, 2023, 7:10pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/2 "2023-05-29T19:10:32Z")

</div>

It would be great if you added some performance comparison with other implementations in the eco system.

---

<div class="post-metadata">

**Author:** ![Taaitaaiger](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/taaitaaiger/32/49792_2.png) [@Taaitaaiger](https://discourse.julialang.org/u/Taaitaaiger)\
**Post date:** [May 29, 2023, 7:28pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/3 "2023-05-29T19:28:36Z")

</div>

Good point! I’ve opened an issue for it

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [May 29, 2023, 7:30pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/4 "2023-05-29T19:30:27Z")

</div>

```julia
julia> using BenchmarkTools, RustFFT, FFTW, LinearAlgebra

julia> v = randn(ComplexF64, 1<<16);

julia> rust_planner64 = RustFFT.FftPlanner64();

julia> rust_instance = RustFFT.plan_fft_forward(rust_planner64, UInt(length(v)));

julia> fftw_plan! = FFTW.plan_fft!(v);

julia> @benchmark RustFFT.fft!($(rust_instance), data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 337.667 μs … 569.458 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 385.834 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 384.350 μs ± 13.164 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

                                  ▁▆█▇▅▁
  ▂▃▃▃▂▂▂▂▂▂▂▂▃▃▃▂▂▂▂▂▂▁▂▂▄▇▇▇▆▆▅▆██████▆▅▄▄▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂ ▃
  338 μs Histogram: frequency by time 423 μs <

 Memory estimate: 64 bytes, allocs estimate: 2.

julia> @benchmark mul!(data, fftw_plan!, data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 314.833 μs … 478.750 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 316.834 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 320.026 μs ± 8.955 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▂▇█▆▃▅▅▄▁▂▂▁ ▁▁▁▁ ▂
  ████████████████████████████▇█▇▇▇▇▇▇▆▇█▇▇▆▇▆▆▅▆▅▅▅▅▄▅▄▅▅▄▅▄▅▆ █
  315 μs Histogram: log(frequency) by time 357 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Seems to be in the same ballpark as FFTW, but `RustFFT.fft!` isn’t fully in-place, there are a couple of allocations.

Feedback: API could be simplified a bit, for example a method

```julia
RustFFT.plan_fft_forward(v::Vector{ComplexF32}) =
    RustFFT.plan_fft_forward(RustFFT.FftPlanner32(), UInt(length(v)))

RustFFT.plan_fft_forward(v::Vector{ComplexF64}) =
    RustFFT.plan_fft_forward(RustFFT.FftPlanner64(), UInt(length(v)))

```

or something like that.

_ **Edit** _: for the record, my platform is

```julia
julia> versioninfo()
Julia Version 1.9.0
Commit 8e630552924 (2023-05-07 11:25 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin22.4.0)
  CPU: 8 × Apple M1
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, apple-m1)
  Threads: 1 on 4 virtual cores

```

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [May 29, 2023, 7:36pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/5 "2023-05-29T19:36:09Z")

</div>

> [@giordano](#):
>
> `julia> v = randn(ComplexF64, 1<<16);`

Some more ‘difficult’ lengths would be interesting.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [May 29, 2023, 7:39pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/6 "2023-05-29T19:39:05Z")

</div>

Just slightly off a power of 2:

```julia
julia> v = randn(ComplexF64, 1<<16 + 7);

julia> rust_planner64 = RustFFT.FftPlanner64();

julia> rust_instance = RustFFT.plan_fft_forward(rust_planner64, UInt(length(v)));

julia> fftw_plan! = FFTW.plan_fft!(v);

julia> @benchmark RustFFT.fft!($(rust_instance), data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 1487 samples with 1 evaluation.
 Range (min … max): 3.024 ms … 3.692 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 3.272 ms ┊ GC (median): 0.00%
 Time (mean ± σ): 3.274 ms ± 62.298 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

                                   ▄▆█▆
  ▂▃▄▃▂▂▂▁▁▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▂▂▂▂▂▃▆█████▇▆▅▅▄▄▄▃▃▃▃▂▃▂▃▂▁▂▁▂ ▃
  3.02 ms Histogram: frequency by time 3.42 ms <

 Memory estimate: 64 bytes, allocs estimate: 2.

julia> @benchmark mul!(data, fftw_plan!, data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 2228 samples with 1 evaluation.
 Range (min … max): 2.101 ms … 3.082 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 2.130 ms ┊ GC (median): 0.00%
 Time (mean ± σ): 2.153 ms ± 56.421 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

    ▇█
  ▃▇██▆▄▃▄▄▂▃▃▃▃▃▃▃▃▃▃▃▄▅▆▇▅▄▃▃▃▃▃▃▃▃▂▃▂▃▂▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂ ▃
  2.1 ms Histogram: frequency by time 2.3 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Using a multiple of either (2,3,5,7), which is still an optimal choice for FFTW:

```julia
julia> v = randn(ComplexF64, nextprod((2,3,5,7), 1<<16+1));

julia> rust_planner64 = RustFFT.FftPlanner64();

julia> rust_instance = RustFFT.plan_fft_forward(rust_planner64, UInt(length(v)));

julia> fftw_plan! = FFTW.plan_fft!(v);

julia> @benchmark RustFFT.fft!($(rust_instance), data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 5304 samples with 1 evaluation.
 Range (min … max): 841.375 μs … 1.117 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 890.855 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 890.873 μs ± 15.784 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

                             ▄█▇█▆▃
  ▁▂▂▂▂▁▁▁▁▂▂▂▁▁▁▁▁▁▁▁▂▃▄▅▅▇████████▆▄▃▃▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  841 μs Histogram: frequency by time 941 μs <

 Memory estimate: 64 bytes, allocs estimate: 2.

julia> @benchmark mul!(data, fftw_plan!, data) setup=(data=copy(v)) evals=1
BenchmarkTools.Trial: 8733 samples with 1 evaluation.
 Range (min … max): 467.458 μs … 893.875 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 468.583 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 475.325 μs ± 46.382 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  █▃ ▁
  ███▇▆▆▅▄▅▃▄▄▄▄▃▁▁▁▁▃▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▆▇ █
  467 μs Histogram: log(frequency) by time 822 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 29, 2023, 8:00pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/7 "2023-05-29T20:00:45Z")

</div>

> [@giordano](#):
>
> `FFTW.plan_fft!(v);`

If you care about performance you should generally pass a planner flag to enable MEASURE or PATIENT mode. Also for 1d transforms FFTW may be faster for out-of-place with preallocated output.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [May 29, 2023, 8:34pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/8 "2023-05-29T20:34:35Z")

</div>

I had tried PATIENT for the 2^16 vector and didn’t find any significant difference.

Side note, I did my benchmarks on aarch64 Darwin, I don’t know how much RustFFT is optimised on this platform, or if we need to turn on special flags in our build on Yggdrasil.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 29, 2023, 9:34pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/9 "2023-05-29T21:34:09Z")

</div>

> [@giordano](#):
>
> Side note, I did my benchmarks on aarch64 Darwin,

Julia’s FFTW build for aarch64 is missing planner support due to a missing configure flag if I recall correctly so that’s why MEASURE made no difference.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [May 29, 2023, 9:59pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/10 "2023-05-29T21:59:55Z")

</div>

On x86\_64

```julia
julia> versioninfo()
Julia Version 1.9.0
Commit 8e630552924 (2023-05-07 11:25 UTC)
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, haswell)
  Threads: 1 on 8 virtual cores

```

Code:

```julia
using BenchmarkTools, RustFFT, FFTW, LinearAlgebra

function bench(N)
    v = randn(ComplexF64, N)
    rust_planner64 = RustFFT.FftPlanner64()
    rust_instance = RustFFT.plan_fft_forward(rust_planner64, UInt(length(v)))
    fftw_plan! = FFTW.plan_fft!(copy(v); flags=FFTW.PATIENT)
    println("RustFFT:")
    display(@benchmark RustFFT.fft!($(rust_instance), data) setup=(data=copy($(v))) evals=1)
    println("FFTW:")
    display(@benchmark mul!(data, $(fftw_plan!), data) setup=(data=copy($(v))) evals=1)
end

```

Benchmarks:

```julia
julia> bench(1<<16)
RustFFT:
BenchmarkTools.Trial: 7155 samples with 1 evaluation.
 Range (min … max): 540.918 μs … 1.741 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 569.428 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 579.482 μs ± 48.960 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

    ▄█▅▄▇▃▁                                                     
  ▂▄████████▆▆▅▄▃▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  541 μs Histogram: frequency by time 801 μs <

 Memory estimate: 64 bytes, allocs estimate: 2.
FFTW:
BenchmarkTools.Trial: 7151 samples with 1 evaluation.
 Range (min … max): 506.168 μs … 1.013 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 573.886 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 583.459 μs ± 33.517 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

                ▄█▆▄▁                                           
  ▂▂▂▂▁▁▁▂▁▂▂▃▄▅█████▇▆▇▆▇█▆█▇▅▅▄▃▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
  506 μs Histogram: frequency by time 715 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> bench(1<<16 + 7)
RustFFT:
BenchmarkTools.Trial: 1458 samples with 1 evaluation.
 Range (min … max): 3.055 ms … 4.598 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 3.263 ms ┊ GC (median): 0.00%
 Time (mean ± σ): 3.296 ms ± 153.412 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▁▂▁ ▃▄▆██▆▅▄▄▃▂▂▁ ▁ ▁
  ████▆▇▇▅▅████████████████▇████▇▅▅▅▄▄▄▅▅▅▆▇▇▇▆▁▄▅▁▁▄▁▅▄▅▄▄▄▄ █
  3.06 ms Histogram: log(frequency) by time 3.91 ms <

 Memory estimate: 64 bytes, allocs estimate: 2.
FFTW:
BenchmarkTools.Trial: 1297 samples with 1 evaluation.
 Range (min … max): 3.619 ms … 6.162 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 3.699 ms ┊ GC (median): 0.00%
 Time (mean ± σ): 3.729 ms ± 134.475 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

        █▇▁▁                                                   
  ▃▃▃▃▃▆████▇▇▇▅▅▄▄▃▃▃▃▃▂▃▂▂▂▂▂▂▂▁▁▁▁▂▁▂▂▁▁▂▃▂▃▁▂▂▂▂▂▁▂▂▁▁▁▁▂ ▃
  3.62 ms Histogram: frequency by time 4.14 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> bench(nextprod((2, 3, 5, 7), 1<<16 + 1))
RustFFT:
BenchmarkTools.Trial: 6508 samples with 1 evaluation.
 Range (min … max): 615.043 μs … 1.369 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 650.530 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 656.128 μs ± 36.265 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

           ▂▄█▅                                                 
  ▂▂▂▃▃▄▄▆▇████▇▆▆▄▄▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▁▁▂▁▂▂▂▂▂▂▂▂▂▂ ▃
  615 μs Histogram: frequency by time 800 μs <

 Memory estimate: 64 bytes, allocs estimate: 2.
FFTW:
BenchmarkTools.Trial: 5800 samples with 1 evaluation.
 Range (min … max): 710.186 μs … 1.572 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 730.624 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 763.972 μs ± 79.095 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▄█▆▆▄▂▂▂▁▃▄▄▃▃▃▂▁▁ ▂
  ████████████████████▇▇▇█▇▇▆▆▆▆▇▇▇▇██▇██▇▆▆▅▅▁▄▃▃▄▅▄▄▅▃▃▅▃▆▇▅ █
  710 μs Histogram: log(frequency) by time 1.14 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Here RustFFT seems to beat FFTW on non-powers-of-2 sizes.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [May 29, 2023, 10:06pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/11 "2023-05-29T22:06:13Z")

</div>

the main advantage is it’s not GPL?

---

<div class="post-metadata">

**Author:** ![Taaitaaiger](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/taaitaaiger/32/49792_2.png) [@Taaitaaiger](https://discourse.julialang.org/u/Taaitaaiger)\
**Post date:** [May 30, 2023, 1:07pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/12 "2023-05-30T13:07:14Z")

</div>

It should be, the library is always compiled with the release flag, and the neon feature should be enabled by default:

> On AArch64, the `neon` feature enables compilation of Neon-accelerated code. This requires rustc 1.61 or newer, and is enabled by default. If this feature is disabled, rustc 1.37 or newer is required.

> On other platforms than AArch64, this feature does nothing and RustFFT will behave like it is not set.

There is some additional overhead, in particular the array and planner are tracked when the FFT is computed to avoid creating multiple mutable references, the returned data is boxed and requires building the `DataType`. That should be a reasonably constant amount of overhead per call, though.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 30, 2023, 2:01pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/13 "2023-05-30T14:01:31Z")

</div>

> [@jling](#):
>
> the main advantage is it’s not GPL?

FFTW also seems a lot more general. It looks like RustFFT doesn’t currently have multidimensional transforms, real-input transforms, DCTs/DSTs, or multi-threading?

---

<div class="post-metadata">

**Author:** ![Taaitaaiger](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/taaitaaiger/32/49792_2.png) [@Taaitaaiger](https://discourse.julialang.org/u/Taaitaaiger)\
**Post date:** [May 30, 2023, 7:10pm UTC](https://discourse.julialang.org/t/ann-rustfft-jl-compute-forward-and-inverse-ffts-with-rustfft/99574/14 "2023-05-30T19:10:19Z")

</div>

Yeah, RustFFT does not support those directly as far as I’m aware. There is RealFFT, written by a contributor to RustFFT, it might be nice add support for that as well.

For this first release I decided I wanted to only release a PoC to show that a binding library could be written using jlrs and that most of the necessary glue code could be generated automatically.
