# Performance of applying a struct as a function

**URL:** https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434
**Category:** New to Julia
**Created:** [May 29, 2025, 1:12pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434 "2025-05-29T13:12:58Z")
**Posts on this page:** 16
**Page:** 1

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 1:12pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/1 "2025-05-29T13:12:59Z")

</div>

I’m not sure what the exact name of this phenomenon is, so I’ll change the title if necessary, but my question involves the following situation: I have some structs that determine how certain data `x` is transformed:

```julia
abstract type FooType end
struct Foo1{T<:Real} <: FooType
    a::T
end 
struct Foo2{T<:Real} <: FooType
    b::T
end

transform(x, a::Foo1) = x .* a.a
transform(x, b::Foo2) = x ./ b.b

```

now, I know that it is possible to also write the following (which I describe as “applying a struct as a function” in the title:

```julia
(a::Foo1)(x) = x .* a.a
(b::Foo2)(x) = x ./ b.b

```

The latter leads to more readable code in my case (in my case, my function name would e.g. be something like `interpolate` while the name which I’ll attach to the instance will be something like `interpolation`, in which case you’d get long expressions like `interpolate(interpolation, x)`. Of course you can abbreviate things, but at some point that increases cognitive overhead for me).

Before I embark on a journey of slowly making a transition to this latter way of writing this throughout my code-base, are there are performance pitfalls I must be aware of? Would implementing `interpolation(x)` in the obvious way just amount to nicer syntax compared to `interpolate(interpolation)`, or is there a difference in how julia treats this?

For what it is worth: I will not be defining only one such method per struct, I need to dispatch based on input types quite often.

---

<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, 2025, 1:29pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/2 "2025-05-29T13:29:48Z")

</div>

> [@JADekker](#):
>
> Would implementing `interpolation(x)` in the obvious way just amount to nicer syntax compared to `interpolate(interpolation)`

Yes. It should have equivalent performance to an ordinary function call.

---

<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, 2025, 1:31pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/3 "2025-05-29T13:31:10Z")

</div>

> [@JADekker](#):
>
> `(a::Foo1)(x) = x .* a.a`

Aside: I would tend to just define `(a::Foo1)(x) = x * a.a`. If you want to vectorize the application of a function, do it at the call site as `a.(x)`. This way it will fuse with other “dot calls”, not to mention being clearer.

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 1:33pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/4 "2025-05-29T13:33:13Z")

</div>

Thanks for confirming! And you’re right of course about the dot-calls!

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 1:37pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/5 "2025-05-29T13:37:48Z")

</div>

Perhaps a follow-up question: what if I have an in-place and a not in-place version of a method, say

```julia
transform(x, a::Foo1) = x * a.a
transform!(x, a::Foo1) = (x *= a.a; x)

```

I can write the first as

```julia
(a::Foo1)(x) = x * a.a

```

but is there a similar way to write the second?

---

<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: [May 29, 2025, 1:41pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/6 "2025-05-29T13:41:51Z")

</div>

> [@JADekker](#):
>
> For what it is worth: I will not be defining only one such method per struct, I need to dispatch based on input types quite often.

Just one thing. If you have vectors of the abstract `FooType`, i.e. some entries are `Foo1`, some are `Foo2`, `Foo3` etc. This dispatch thing may not be the most efficient. In that case it might be faster to do manual dispatch based on a stored `Int` or something.

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 1:43pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/7 "2025-05-29T13:43:11Z")

</div>

Thanks for the reminder! My use-case fortunately involves only one `FooType` at the time, I’m just dispatching on the type of `x`.

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 2:24pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/8 "2025-05-29T14:24:32Z")

</div>

Just to clarify (note the `!`):

```julia
    (a::Foo1)!(x) = (x *= a.a; x)

```

will give an error, but is intuitively kind of what I’m after. Is the simplest approach really to add an additional argument to dispatch on so I can get the in-place version, such as:

```julia
(a::Foo)(x, ::Nothing) = (x *= a.a; x)

```

or is there an elegant solution?

---

<div class="post-metadata">

### Author: ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)
#### Post date: [May 29, 2025, 2:35pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/9 "2025-05-29T14:35:28Z")

</div>

Note that `x *= a.a` is not an inplace operation, it’s just a rebinding.

And yes, if you want a different method you need a different dispatch. One thing you could do which is maybe a bit too ‘cute’ would be this:

```julia
(a::Foo)(::typeof(!), x) = (x .*= a.a; x)

```

and then you’d have

```julia
a(!, x)

```

mean the in-place version. Probably best to just use `transform!` or whatever though here instead of callable structs.

---

<div class="post-metadata">

### Author: ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)
#### Post date: [May 29, 2025, 2:37pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/10 "2025-05-29T14:37:22Z")

</div>

I’d do

```julia
f = Foo()
x .= f.(x)

```

---

<div class="post-metadata">

### Author: ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)
#### Post date: [May 29, 2025, 2:58pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/11 "2025-05-29T14:58:56Z")

</div>

Yes, if the in-place logic is simple enough that it doesn’t actually need to be in its own function and everything can be thought of elementwise that’s definitely nicer. Sometimes the real cases are a lot messier than that though and one does want it wrapped up in a function.

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 3:23pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/12 "2025-05-29T15:23:54Z")

</div>

These are all good points, I’ve written a slightly better MWE. If I’m going with the fourth version (call syntax, in-place), then I’d probably use a different symbol to avoid accidentally triggering it if I made a mistake while typing a negation…

```julia
using BenchmarkTools

struct Foo1 a::Float64 end

# Apply function
apply_foo(foo::Foo1, x::AbstractVector) = x .- (foo.a * mean(x))
# Apply function in-place
apply_foo!(foo::Foo1, x::AbstractVector) = (x .-= foo.a * mean(x); x)
# Apply function using call syntax
(foo::Foo1)(x::AbstractVector) = x .- (foo.a * mean(x))
# Apply function using call syntax, in-place
(::typeof(!))(foo::Foo1, x::AbstractVector) = (x .-= foo.a * mean(x); x)

function test()
    foo = Foo1(0.1)
    x = [1.,2.,3.]

    # Benchmarking different ways to apply the function
    apply_foo(foo, x); apply_foo!(foo, x); foo(x); !(foo, x) # Warm up
    display(@benchmark apply_foo($foo, $x))
    display(@benchmark apply_foo!($foo, $x))
    display(@benchmark ($foo)($x))
    display(@benchmark !($foo, $x))
end
test()

```

---

<div class="post-metadata">

### Author: ![JADekker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jadekker/32/210281_2.png) [@JADekker](https://discourse.julialang.org/u/JADekker)
#### Post date: [May 29, 2025, 3:24pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/13 "2025-05-29T15:24:23Z")

</div>

Gives

```julia
BenchmarkTools.Trial: 10000 samples with 998 evaluations per sample.
 Range (min … max): 18.578 ns … 48.754 μs ┊ GC (min … max): 0.00% … 99.91%
 Time (median): 22.754 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 28.048 ns ± 487.510 ns ┊ GC (mean ± σ): 17.84% ± 1.39%

  ▂▆▄ ▃▄▄▅██▇▆▅▄▃▃▂▂▁▁▁▁▁ ▁ ▂
  ████▆▆▇█▆██████████████████████▇██▇█▇▇▆▆▆▇▆▆▅▇▅▆▂▅▅▅▄▄▄▄▂▄▄▃ █
  18.6 ns Histogram: log(frequency) by time 36 ns <

 Memory estimate: 80 bytes, allocs estimate: 1.
BenchmarkTools.Trial: 10000 samples with 1000 evaluations per sample.
 Range (min … max): 9.833 ns … 58.625 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 10.250 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 10.429 ns ± 1.441 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

     ▅▇█▄ ▁ ▁
  ▅▇▆████▆▄▃▇█▇▆▆▆▇▆▅▅▅▅▅▃▅▃▄▄▄▅▃▅▄▂▄▄▄▅▃▃▄▄▄▄▄▃▂▄▄▆▆▅▄▅▅▅▆▆▅ █
  9.83 ns Histogram: log(frequency) by time 14.7 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.
BenchmarkTools.Trial: 10000 samples with 998 evaluations per sample.
 Range (min … max): 18.537 ns … 48.947 μs ┊ GC (min … max): 0.00% … 99.90%
 Time (median): 21.585 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 27.304 ns ± 489.328 ns ┊ GC (mean ± σ): 18.20% ± 1.39%

  ▁▅▃ ▅▆█▆▅▄▃▂▃▂▃▂▂▁▁▁▁ ▂
  ████▆▇██████████████████████▇▇▇▇▇▇▆▇▆▅▆▆▆▅▄▅▅▄▅▄▆▄▁▄▄▅▄▄▅▃▅▃ █
  18.5 ns Histogram: log(frequency) by time 40.1 ns <

 Memory estimate: 80 bytes, allocs estimate: 1.
BenchmarkTools.Trial: 10000 samples with 1000 evaluations per sample.
 Range (min … max): 9.792 ns … 45.917 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 10.209 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 10.408 ns ± 1.349 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

     ▄██▅ ▁▁ ▂
  ███████▆▄▅▆██▆▆▆▇▇▇▅▅▆▅▅▅▁▃▅▅▄▅▅▄▅▅▃▅▄▅▅▅▃▅▅▅▃▅▅▅▇▆▅▆▄▃▇▆▇▆ █
  9.79 ns Histogram: log(frequency) by time 14.7 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

---

<div class="post-metadata">

### Author: ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)
#### Post date: [May 30, 2025, 8:17am UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/14 "2025-05-30T08:17:43Z")

</div>

What I usually do when my function is too complex (e.g. because you need to preallocate the outputs) such that the simple solution by @mbauman works

> [@mbauman](#):
>
> I’d do
> 
> ```julia
> f = Foo()
> x .= f.(x)
> 
> ```

In this case, I just use different arities:

```julia
(a::Foo1)(x) = a(similar(x), x) # return freshly allocated result
(a::Foo1)(out, x) = out .= x .* a.a # writes result into existing memory

```

the downside is of course that you lose the `!` for the mutating call.

---

<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, 2025, 2:28pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/15 "2025-05-30T14:28:32Z")

</div>

> [@abraemer](#):
>
> the downside is of course that you lose the `!` for the mutating call.

For an in-place/mutating method I would just use a different function name with a `!`, e.g. `evalfoo!(foo, x)`. This way you follow the standard Julia convention for naming mutating functions.

Yes, it’s a little more verbose, but it’s worth it IMO to be explicit about the side effect. Code that uses mutating functions is usually lower-level performance-critical code anyway, which can afford to be more verbose.

But at this point we’re down to [bikeshedding](https://en.wikipedia.org/wiki/Law_of_triviality), not performance questions as in the original post.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [May 30, 2025, 3:17pm UTC](https://discourse.julialang.org/t/performance-of-applying-a-struct-as-a-function/129434/16 "2025-05-30T15:17:23Z")

</div>

One way to avoid bikeshedding for the mutating function name is to instead implement the MutableArithmetics.jl interface (`operate!`) for the non-mutating version, providing the mutating version.
