oldhomemovie
oldhomemovie

Reputation: 15139

Issue with variable inside a binary match

Given this code:

defmodule MyModule do
  def my_func(<< part::binary-size(size), rest::binary >>, size) do
    IO.puts(part)
  end

  def my_func_2(size, << part::binary-size(size), rest::binary >>) do
    IO.puts(part)
  end

  def my_func_3(size, << part::binary-size(size-1), rest::binary >>) do
    IO.puts(part)
  end
end

The module won't compile. If I comment out all functions but one, I get different errors:

  1. Compiling with my_func/1 gives me this error:

    warning: variable "size" does not exist and is being expanded to "size()", please use parentheses to remove the ambiguity or change the variable name
      my_script.exs:2
    
    ** (CompileError) my_script.exs:2: size in bitstring expects an integer or a variable as argument, got: size()
        (elixir) src/elixir_bitstring.erl:52: :elixir_bitstring.expand_bit_info/5
        (elixir) src/elixir_bitstring.erl:29: :elixir_bitstring.expand_bitstr/4
        (elixir) src/elixir_bitstring.erl:10: :elixir_bitstring.expand/3
        (stdlib) lists.erl:1354: :lists.mapfoldl/3
    
  2. Compiling with my_func_2/1 gives me another error:

    ** (CompileError) my_script.exs:6: variable size@1 is unbound
        (stdlib) lists.erl:1338: :lists.foreach/2
        (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    
  3. Compiling with my_func_3/1 gives me a different error:

    ** (CompileError) my_script.exs:10: size in bitstring expects an integer or a variable as argument, got: :erlang.-(size, 1)
        (elixir) src/elixir_bitstring.erl:52: :elixir_bitstring.expand_bit_info/5
        (elixir) src/elixir_bitstring.erl:29: :elixir_bitstring.expand_bitstr/4
        (elixir) src/elixir_bitstring.erl:10: :elixir_bitstring.expand/3
        (stdlib) lists.erl:1354: :lists.mapfoldl/3
        (stdlib) lists.erl:1355: :lists.mapfoldl/3
    

Can someone explain all errors to me? What is happening at the erlang compiler level and why?

Upvotes: 1

Views: 525

Answers (1)

tkowal
tkowal

Reputation: 9299

Erlang and Elixir fooled us to believe that we can do nested pattern matching on every datastructure, but binaries are an exception. Here you can read that

Binary patterns cannot be nested (...) segments have the following general syntax:

Value:Size/TypeSpecifierList

When matching Value, value must be either a variable or an integer, or a floating point literal. Expressions are not allowed.

Size must be an integer literal, or a previously bound variable. The following is not allowed:

foo(N, <<X:N,T/binary>>) ->
  {X,T}.

The two occurrences of N are not related. The compiler will complain that the N in the size field is unbound.

The correct way to write this example is as follows:

foo(N, Bin) ->
  <<X:N,T/binary>> = Bin,
  {X,T}.

This is also true for Elixir.

So in my_func compile doesn't try to look in the function head after the first parameter and says that there is no variable called size. It figures that it might be a function and then compiler blows up with an error saying that size in bitstring expects an integer or a variable as argument.

In my_func_2 things get tricky, because the variable is there in the first argument, but it is not yet bound. This is exactly the case from Erlang documenation above. You can't pattern match on the size. It must be previously bound variable. The name size@1 hints you that compiler doesn't treat the two as the same variable. They have different names internally.

In my_func_3 compiler sees that there is a subtraction so it doesn't bother to check for variables and immediately reports that you can't use expressions inside binary pattern match.

The correct way to work around this limitation is also in the quoted part.

Upvotes: 2

Related Questions