# When should a function accept a symbol as an argument?

**URL:** <https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510>\
**Category:** New to Julia\
**Tags:** question, metaprogramming, design\
**Created:** [July 22, 2020, 6:51pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510 "2020-07-22T18:51:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![clarkfitzg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/clarkfitzg/32/15373_2.png) [@clarkfitzg](https://discourse.julialang.org/u/clarkfitzg)\
**Post date:** [July 22, 2020, 6:51pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/1 "2020-07-22T18:51:26Z")

</div>

I stumbled across two situations where functions accept symbols as arguments, and wonder why they are designed that way. What advantages are there to using symbols? When should I write functions that accept symbols instead of strings or booleans? Here are the examples:

1. In the Debugger, to break on error:

```julia
Debugger.break_on(:error)

```

Why not write `break_on` to accept strings, for example, `Debugger.break_on("error")`?

1. To get the labels for the MNIST test data set:

```julia
Flux.Data.MNIST.labels(:test)

```

The labels can be either test or train, so why not use something like `Flux.Data.MNIST.labels(test = true)`?

@StefanKarpinski’s [answer to “what is a Symbol?”](https://stackoverflow.com/questions/23480722/what-is-a-symbol-in-julia) answers this question for DataFrames, but I don’t see how the reasoning applies in the cases above. The [docs on metaprogramming](https://docs.julialang.org/en/latest/manual/metaprogramming/) explain what is happening, and some of the cool things you can do with it, but not why you would want to for these use cases.

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [July 22, 2020, 7:30pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/2 "2020-07-22T19:30:18Z")

</div>

It might primarily be an aesthetic preference. I find `:error` to be slightly less visually noisy than `"error"`. Text editors also often color symbols and strings different colors:

![image](https://global.discourse-cdn.com/julialang/original/3X/5/4/54239da8a8dd30ed4db1be58562442eb52b4d18e.png)

Symbols are not the same as `Enum`s, of course, but I tend to think of them as better than strings when you want a lightweight enum, like `:yes`, `:no`, `:maybe`.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 22, 2020, 9:23pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/3 "2020-07-22T21:23:14Z")

</div>

Symbols are similar to a global enumeration. If creating an enumeration just for that case is overkill, and the symbols are few and short, then using `Symbol` is often a sensible choice. Using a string would work too, but comparison of Strings is costlier, I think?

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [July 22, 2020, 11:05pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/4 "2020-07-22T23:05:34Z")

</div>

> [@Henrique\_Becker](#):
>
> Using a string would work too, but comparison of Strings is costlier, I think?

It seems so (although the numbers are so small that I may be measuring something else):

```julia
julia> @btime "abcd" == "abcd"
  5.632 ns (0 allocations: 0 bytes)
true

julia> @btime :abcd == :abcd
  0.025 ns (0 allocations: 0 bytes)
true

julia> @btime "abcd" == "fghi"
  7.237 ns (0 allocations: 0 bytes)
false

julia> @btime :abcd == :fghi
  0.025 ns (0 allocations: 0 bytes)
false

```

Regarding binary inputs (true/false), I don’t think that Symbols may compete with Booleans, so in that case I suppose that the reason to choose them is usability (no need to remember if `true` refers to train or test).

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [July 22, 2020, 11:31pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/5 "2020-07-22T23:31:04Z")

</div>

> [@Henrique\_Becker](#):
>
> comparison of Strings is costlier, I think?

I think for most code that uses symbols or strings for mode selection, whatever difference in performance there might be between strings and symbols is negligible. For example, in this code

```julia
function foo(x; mode=:a)
    if mode == :a
        foo_a(x)
    else
        foo_b(x)
    end
end

```

the cost of the `mode == :a` comparison is most likely negligible compared to the cost of running `foo_a` or `foo_b`.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 22, 2020, 11:38pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/6 "2020-07-22T23:38:00Z")

</div>

Yes, you are correct (except, maybe, if the code would allocate nothing if you used symbols, and now it has to allocate strings for each comparison inside a loop or something like that).

However, often, there is no good reason to use a `String` in place of `Symbol` for such cases, and the balance ends up tipping to the `Symbol` side. Even if is just because experienced programmers expect it to be a `Symbol`.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 22, 2020, 11:40pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/7 "2020-07-22T23:40:42Z")

</div>

I get considerably different values if I do not interpolate, but in any case, it seems that comparing symbols is indeed cheaper.

```julia
julia> a = "0123456789"
"0123456789"

julia> b = "0123456789"
"0123456789"

julia> using BenchmarkTools

julia> s_a = :0123456789
123456789

julia> s_b = :0123456789
123456789

julia> @btime a == b
  15.003 ns (0 allocations: 0 bytes)
true

julia> @btime s_a == s_b
  11.712 ns (0 allocations: 0 bytes)
true

julia> @btime $a == $b
  2.658 ns (0 allocations: 0 bytes)
true

julia> @btime $s_a == $s_b
  0.016 ns (0 allocations: 0 bytes)
true

```

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [July 22, 2020, 11:43pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/8 "2020-07-22T23:43:10Z")

</div>

`:0123456789` returns an integer, not a symbol. 🙂

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 22, 2020, 11:52pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/9 "2020-07-22T23:52:47Z")

</div>

Argh, betrayal.

Apparently, I cannot interpolate `Symbol`s with `@btime`?

```julia
julia> s_a = Symbol("A0123456789")
:A0123456789

julia> s_b = Symbol("A0123456789")
:A0123456789

julia> using BenchmarkTools

julia> @btime $s_a == $s_b
ERROR: UndefVarError: A0123456789 not defined
[...]

```

---

<div class="post-metadata">

**Author:** ![clarkfitzg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/clarkfitzg/32/15373_2.png) [@clarkfitzg](https://discourse.julialang.org/u/clarkfitzg)\
**Post date:** [July 23, 2020, 12:24am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/10 "2020-07-23T00:24:11Z")

</div>

> It might primarily be an aesthetic preference.

This is what I suspected.

---

<div class="post-metadata">

**Author:** ![clarkfitzg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/clarkfitzg/32/15373_2.png) [@clarkfitzg](https://discourse.julialang.org/u/clarkfitzg)\
**Post date:** [July 23, 2020, 12:37am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/11 "2020-07-23T00:37:56Z")

</div>

Using expressions does put more cognitive load on new users, as it’s another bit of syntax and concept to understand. It’s also a bit intimidating, because it leads one down the path of metaprogramming.

If using a symbol only gets us a slightly cleaner style, I don’t feel it’s worth the additional complexity over a string.

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [July 23, 2020, 12:42am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/12 "2020-07-23T00:42:10Z")

</div>

> [@Henrique\_Becker](#):
>
> Apparently, I cannot interpolate `Symbol` s with `@btime` ?

```julia
julia> s_a = Symbol("0123456789")
Symbol("0123456789")

julia> s_b = Symbol("0123456789")
Symbol("0123456789")

julia> @benchmark $(QuoteNode(s_a)) == $(QuoteNode(s_b))
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 0.016 ns (0.00% GC)
  median time: 0.019 ns (0.00% GC)
  mean time: 0.019 ns (0.00% GC)
  maximum time: 0.028 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

```

FWIW, sub-ns times generally mean the compiler defeated the benchmark.

---

<div class="post-metadata">

**Author:** ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)\
**Post date:** [July 23, 2020, 1:43am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/13 "2020-07-23T01:43:30Z")

</div>

I believe you need the `Ref` trick:

```julia
julia> @benchmark $(Ref(QuoteNode(s_a)))[] == $(Ref(QuoteNode(s_b)))[]
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 14.906 ns (0.00% GC)
  median time: 15.678 ns (0.00% GC)
  mean time: 16.885 ns (0.00% GC)
  maximum time: 66.014 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 997

```

---

<div class="post-metadata">

**Author:** ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)\
**Post date:** [July 23, 2020, 1:44am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/14 "2020-07-23T01:44:48Z")

</div>

Strangely (to me) strings seem to compare faster:

```julia
julia> s1 = "hello"
"hello"

julia> s2 = "hello"
"hello"

julia> @benchmark $(Ref(s1))[] == $(Ref(s2))[]
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 6.783 ns (0.00% GC)
  median time: 8.304 ns (0.00% GC)
  mean time: 8.162 ns (0.00% GC)
  maximum time: 38.303 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 999

```

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [July 23, 2020, 1:47am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/15 "2020-07-23T01:47:51Z")

</div>

> [@dpsanders](#):
>
> Strangely (to me) strings seem to compare faster:

FWIW, in my code I always use `===` for symbols. This is faster than `String`s for me:

```julia
julia> s_a = Symbol("0123456789")
Symbol("0123456789")

julia> s_b = Symbol("0123456789")
Symbol("0123456789")

julia> @benchmark $(Ref(QuoteNode(s_a)))[] == $(Ref(QuoteNode(s_b)))[]
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 12.634 ns (0.00% GC)
  median time: 12.863 ns (0.00% GC)
  mean time: 12.908 ns (0.00% GC)
  maximum time: 36.339 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 998

julia> @benchmark $(Ref(QuoteNode(s_a)))[] === $(Ref(QuoteNode(s_b)))[]
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 2.840 ns (0.00% GC)
  median time: 3.059 ns (0.00% GC)
  mean time: 3.070 ns (0.00% GC)
  maximum time: 15.963 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

julia> @btime $(Ref(QuoteNode(s_a)))[] === $(Ref(QuoteNode(s_b)))[]
  2.626 ns (0 allocations: 0 bytes)
true

julia> s1 = "hello"
"hello"

julia> s2 = "hello"
"hello"

julia> @benchmark $(Ref(s1))[] == $(Ref(s2))[]
BenchmarkTools.Trial:
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 4.363 ns (0.00% GC)
  median time: 4.369 ns (0.00% GC)
  mean time: 4.386 ns (0.00% GC)
  maximum time: 26.813 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

```

---

<div class="post-metadata">

**Author:** ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)\
**Post date:** [July 23, 2020, 2:25am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/16 "2020-07-23T02:25:31Z")

</div>

According to the [documentation](https://docs.julialang.org/en/v1/manual/metaprogramming/#Symbols-1) a symbol is an “interned string”, meaning only one copy of it is stored. I think this means that when code containing a symbol is compiled, the symbol is effectively replaced by its unique id (i.e. a pointer). This makes storing, sharing, and comparing symbols efficient because they are essentially just addresses, whereas a string is a sequence of an arbitrary number of bytes.

In my experience, symbols are often used (as @CameronBieganek suggested) as quick-and-easy [categorical values](https://en.wikipedia.org/wiki/Categorical_variable).

---

<div class="post-metadata">

**Author:** ![bkamins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bkamins/32/208538_2.png) [@bkamins](https://discourse.julialang.org/u/bkamins)\
**Post date:** [July 23, 2020, 9:17am UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/17 "2020-07-23T09:17:59Z")

</div>

Some comments (that are the consequences of what was already said):

1. `Symbol`s are not deallocated in a single Julia session which means that they make sense if there is only a limited number of them (for `if`s it is the case)
2. `Symbol` is always treated as a whole (so string operation functions do not work on `Symbol`s - you have to convert them to string first), which sometimes is a limitation
3. `Symbol`s that are not valid identifiers are relatively cumbersome to spell out (which matters in interactive use)

E.g. consideration of points 2 and 3 made us start accepting strings as column names in DataFrames.jl.

---

<div class="post-metadata">

**Author:** ![tisztamo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tisztamo/32/16200_2.png) [@tisztamo](https://discourse.julialang.org/u/tisztamo)\
**Post date:** [July 23, 2020, 1:44pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/18 "2020-07-23T13:44:25Z")

</div>

Accessing struct and `NamedTuple` fields is possible and fast (inlined) by symbols, which may open up optimization possibilities that are unforeseeable at API design time. Also, `Dict{Symbol, Any}` is faster than `Dict{String, Any}`, while converting a `String` to a `Symbol` is relatively slow.

As those optimization possibilities cannot always be seen at API design time, I generally find it better to use symbols for mode selection.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 23, 2020, 6:45pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/19 "2020-07-23T18:45:37Z")

</div>

I have to say that for me, the profit I got from `Dataframes.jl` accepting strings as column names, is that I can use `Symbol`s with the special meaning given by the macros of `DataFramesMeta.jl` (i.e., the column itself), but also have a simple way to refer to the column names without escaping the symbols inside the same expression.

---

<div class="post-metadata">

**Author:** ![clarkfitzg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/clarkfitzg/32/15373_2.png) [@clarkfitzg](https://discourse.julialang.org/u/clarkfitzg)\
**Post date:** [July 23, 2020, 6:59pm UTC](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510/20 "2020-07-23T18:59:02Z")

</div>

Thanks all for the in depth responses. It sounds that using symbols versus strings may have some performance impact on the order of 10ns, which may matter for some applications. This impact comes from implementation details such as inlining, allocations, and string interning.

As a naive new user, symbols appear unusual and noteworthy, which is why I asked the original question. There are certainly plenty of examples that use strings over symbols, for example:

```julia
open("somefile.txt", "w+")

```

Opening a file like this is a familiar pattern across many languages, and it would be noteworthy indeed if Julia did something like:

```julia
open("somefile.txt", :w+)

```

The above doesn’t work, because `w+` is not a symbol. It appears it’s not even possible to express `w+` with an expression, because `w+` is a symbol with a binary operator, which isn’t a complete expression.

Hence symbols have syntactic restrictions that strings do not, which is another disadvantage.

[Next page](https://discourse.julialang.org/t/when-should-a-function-accept-a-symbol-as-an-argument/43510.md?page=2)
