# Why does defining a vararg method define a zero argument method?

**URL:** https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872
**Category:** Internals & Design
**Tags:** question
**Created:** [November 27, 2020, 5:10pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872 "2020-11-27T17:10:41Z")
**Posts on this page:** 13
**Page:** 2

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [November 30, 2020, 2:12pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/21 "2020-11-30T14:12:04Z")

</div>

The beauty of `f(x, xs...)` is in _not_ needing special syntax for this.

When it comes to syntax, less is more.

---

<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: [November 30, 2020, 2:19pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/22 "2020-11-30T14:19:52Z")

</div>

> [@Tamas\_Papp](#):
>
> ```julia
> f(x, xs...)
> 
> ```

It’s probably more of an issue when you have a substantial type signature, like:

```julia
f(x::SomeType{A}, xs::SomeType{A}...)

```

Then maybe this is better

```julia
f(x::T, xs::T...) where {T<:SomeType{A}}

```

---

<div class="post-metadata">

### Author: ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)
#### Post date: [December 1, 2020, 12:38am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/23 "2020-12-01T00:38:42Z")

</div>

Hi, I have two questions.  
In this case

> [@Tamas\_Papp](#):
>
> `f(x, xs...) = ...`

1. What is the best way to pass a collection?
2. What is the best way to recover it inside the function?

Here and example

```julia
using BenchmarkTools

fun1(x, xs...) = (x, xs...)
fun2(xs...) = xs

xs = rand(Int(1e3))
x0, x1 = xs[1], xs[2:end]
@btime fun1($x0, $x1...) 
# 70.376 μs (2008 allocations: 78.72 KiB)
@btime fun2($xs...);
# 35.817 μs (1003 allocations: 39.38 KiB)

```

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [December 1, 2020, 1:10am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/24 "2020-12-01T01:10:37Z")

</div>

I would also add that I cannot think of a case where I have to write this and then put the first element back into a tuple with the rest. You almost always have to recurse so that having the first element is useful or you should be defining the single element method explicitly.

---

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [December 1, 2020, 8:27am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/25 "2020-12-01T08:27:25Z")

</div>

> [@josePereiro](#):
>
> What is the best way to pass a collection?

As an argument, plain and simple. Splatting it is not idiomatic and incurs a significant compilation and performance penalty.

---

<div class="post-metadata">

### Author: ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)
#### Post date: [December 1, 2020, 10:42am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/26 "2020-12-01T10:42:33Z")

</div>

For

```julia
fn(x, xs...) = (x, xs...)

```

you don’t need to split a collection into the first element and the rest, Julia does that automatically.  
In your example, calling `fun1(xs...)` would work just fine (except for extra allocations).

---

<div class="post-metadata">

### Author: ![josePereiro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/josepereiro/32/17322_2.png) [@josePereiro](https://discourse.julialang.org/u/josePereiro)
#### Post date: [December 1, 2020, 2:13pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/27 "2020-12-01T14:13:44Z")

</div>

> [@Tamas\_Papp](#):
>
> As an argument, plain and simple. Splatting it is not idiomatic and incurs a significant compilation and performance penalty.

Got it!

> [@Vasily\_Pisarev](#):
>
> In your example, calling `fun1(xs...)` would work just fine (except for extra allocations).

Nice, I didn’t know

Thanks

---

<div class="post-metadata">

### Author: ![malacroi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/malacroi/32/19745_2.png) [@malacroi](https://discourse.julialang.org/u/malacroi)
#### Post date: [December 2, 2020, 3:01am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/28 "2020-12-02T03:01:23Z")

</div>

> [@StefanKarpinski](#):
>
> I would also add that I cannot think of a case where I have to write this and then put the first element back into a tuple with the rest.

Exactly this situation came up to make small inert polynomials by summing terms. The intention is that `Terms` get collected now, to be iterated later, and the consumer might choose in which order to deal with them.

```julia
struct Term
    coefficient::Int64
    degree::Int64
end
struct PolyTerm{N}
    pt::NTuple{N,Term}
end
import Base: +
+(s::Term, t::Term...) = PolyTerm((s,t...))

```

I really want to write `+(pt::Term...)=PolyTerm(pt)`, to avoid splitting and repacking my Tuple, but then I’ve accidentally gone and pirated `+()` to create a `PolyTerm{0}` instead of the `MethodError` people may have been counting on.

---

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [December 2, 2020, 8:09am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/29 "2020-12-02T08:09:04Z")

</div>

> [@malacroi](#):
>
> avoid splitting and repacking my Tuple

Why? I did not check, but ideally it should compile to the same code.

---

<div class="post-metadata">

### Author: ![malacroi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/malacroi/32/19745_2.png) [@malacroi](https://discourse.julialang.org/u/malacroi)
#### Post date: [December 2, 2020, 8:31am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/30 "2020-12-02T08:31:20Z")

</div>

They don’t, at least not at the `@code_llvm` level (is there another optimization step after that? either way, there’s either a difference in run-time for compiled code or compile time between the two versions). Edited to add a MWE

```julia
struct Goose i::Int64 ; j::Int64 end
struct Flock{N} g::NTuple{N,Goose} end

Waddle(g::Goose...) = Flock(g)
Swim(g::Goose,moregeese::Goose...) = Flock((g,moregeese...))

@code_llvm Waddle(Goose(1,2), Goose(2,3), Goose(3,4))
@code_llvm Swim(Goose(1,2), Goose(2,3), Goose(3,4))

```

gives (sorry, not sure how to nicely format output)

```julia
; @ REPL[3]:1 within `Waddle'
define void @julia_Waddle_170([1 x [3 x [2 x i64]]]* noalias nocapture sret, [2 x i64]* nocapture nonnull readonly dereferenceable(16), [2 x i64]* nocapture nonnull readonly dereferenceable(16), [2 x i64]* nocapture nonnull readonly dereferenceable(16)) {
top:
  %4 = alloca [3 x [2 x i64]], align 8
  %5 = bitcast [3 x [2 x i64]]* %4 to i8*
  %6 = bitcast [2 x i64]* %1 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %5, i8* nonnull align 1 %6, i64 16, i1 false)
  %7 = getelementptr inbounds [3 x [2 x i64]], [3 x [2 x i64]]* %4, i64 0, i64 1
  %8 = bitcast [2 x i64]* %7 to i8*
  %9 = bitcast [2 x i64]* %2 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %8, i8* nonnull align 1 %9, i64 16, i1 false)
  %10 = getelementptr inbounds [3 x [2 x i64]], [3 x [2 x i64]]* %4, i64 0, i64 2
  %11 = bitcast [2 x i64]* %10 to i8*
  %12 = bitcast [2 x i64]* %3 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %11, i8* nonnull align 1 %12, i64 16, i1 false)
  %13 = bitcast [1 x [3 x [2 x i64]]]* %0 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %13, i8* nonnull align 8 %5, i64 48, i1 false)
  ret void
}

```

vs

```julia
; @ REPL[4]:1 within `Swim'
define void @julia_Swim_171([1 x [3 x [2 x i64]]]* noalias nocapture sret, [2 x i64]* nocapture nonnull readonly dereferenceable(16), [2 x i64]* nocapture nonnull readonly dereferenceable(16), [2 x i64]* nocapture nonnull readonly dereferenceable(16)) {
top:
  %4 = alloca [2 x [2 x i64]], align 8
  %5 = alloca [3 x [2 x i64]], align 8
  %moregeese = alloca [2 x [2 x i64]], align 8
  %6 = bitcast [2 x [2 x i64]]* %4 to i8*
  %7 = bitcast [2 x i64]* %2 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %6, i8* nonnull align 1 %7, i64 16, i1 false)
  %8 = getelementptr inbounds [2 x [2 x i64]], [2 x [2 x i64]]* %4, i64 0, i64 1
  %9 = bitcast [2 x i64]* %8 to i8*
  %10 = bitcast [2 x i64]* %3 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %9, i8* nonnull align 1 %10, i64 16, i1 false)
  %11 = bitcast [2 x [2 x i64]]* %moregeese to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %11, i8* nonnull align 8 %6, i64 32, i1 false)
  %12 = getelementptr inbounds [2 x [2 x i64]], [2 x [2 x i64]]* %moregeese, i64 0, i64 1
  %13 = bitcast [3 x [2 x i64]]* %5 to i8*
  %14 = bitcast [2 x i64]* %1 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %13, i8* nonnull align 1 %14, i64 16, i1 false)
  %15 = getelementptr inbounds [3 x [2 x i64]], [3 x [2 x i64]]* %5, i64 0, i64 1
  %16 = bitcast [2 x i64]* %15 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %16, i8* nonnull align 8 %6, i64 16, i1 false)
  %17 = getelementptr inbounds [3 x [2 x i64]], [3 x [2 x i64]]* %5, i64 0, i64 2
  %18 = bitcast [2 x i64]* %17 to i8*
  %19 = bitcast [2 x i64]* %12 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %18, i8* nonnull align 8 %19, i64 16, i1 false)
  %20 = bitcast [1 x [3 x [2 x i64]]]* %0 to i8*
; ┌ @ REPL[2]:1 within `Flock' @ REPL[2]:1
   call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %20, i8* nonnull align 8 %13, i64 48, i1 false)
; └
  ret void
}

```

The distinction disappears if `Goose` has a single field.

---

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [December 2, 2020, 9:38am UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/31 "2020-12-02T09:38:05Z")

</div>

> [@malacroi](#):
>
> Edited to add a MWE

Thanks, this is interesting. `@btime` reports no performance difference, so I am not sure if this is an issue that needs to (can be) addressed.

My point was that in theory, I don’t see a compelling reason why the two forms _should_ be effectively different.

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [December 2, 2020, 1:51pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/32 "2020-12-02T13:51:47Z")

</div>

That’s a good example. As you observe, it only needs the “at least one” property in order to avoid piracy and it happens to want to create a tuple internally, so the “at least one splat” would be really convenient here. If you were defining a function that you “owned” then the zero args method would be fine, e.g.:

```julia
PolyTerm(args::Term...) = PolyTerm{length(args)}(args)

```

```julia
julia> PolyTerm()
PolyTerm{0}(())

julia> PolyTerm(Term(2,3))
PolyTerm{1}((Term(2, 3),))

```

---

<div class="post-metadata">

### Author: ![BioTurboNick](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bioturbonick/32/6380_2.png) [@BioTurboNick](https://discourse.julialang.org/u/BioTurboNick)
#### Post date: [February 4, 2021, 8:53pm UTC](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872/33 "2021-02-04T20:53:42Z")

</div>

Seems like a good spot to mention an issue I was looking into.

In some situations, a splatted/slurped tuple leads to extra allocations over a tuple passed directly, when the tuple is used within an unoptimized throw block.

[Extra allocations associated with unused ArgumentError · Issue #37639 · JuliaLang/julia (github.com)](https://github.com/JuliaLang/julia/issues/37639)

It’s a bit maddening because I can’t see any obvious place in the LLVM where the additional allocation is occurring. And yet, all I need to do to eliminate the extra allocation is either 1) pass a complete tuple directly to the function or 2) ensure the slurped tuple is not used within the unoptimized block.

So maybe it’s rare, but sometimes they do compile differently.

[Previous page](https://discourse.julialang.org/t/why-does-defining-a-vararg-method-define-a-zero-argument-method/50872.md?page=1)
