Argument error if I specify size for an existing bitstring when trying to build Elixir bitstring

Viewed 52

I'm working on this exercism problem and trying to pull 7 bits out of a bitstring, attach a leading / marker bit at the start of the byte and append both to the start of another accumulator bitstring.

In the process, I've stumbled on a confusing error, that unhelpfully is just called ArgumentError, if I try to specify the size of something that's already a bitstring.

Here's an example:

iex(28)> a = <<64::size(7)>>
<<64::size(7)>>
iex(29)> b = <<1::size(1)>>
<<1::size(1)>>
iex(30)> <<b::size(1), a::size(7)>>
** (ArgumentError) argument error while evaluating iex at line 30
    (stdlib 4.0) eval_bits.erl:143: :eval_bits.eval_exp_field/6
    (stdlib 4.0) eval_bits.erl:77: :eval_bits.create_binary/2
    (stdlib 4.0) eval_bits.erl:68: :eval_bits.expr_grp/5
    (stdlib 4.0) erl_eval.erl:543: :erl_eval.expr/6
    (iex 1.13.4) lib/iex/evaluator.ex:310: IEx.Evaluator.handle_eval/3
iex(30> # of course, this works
iex(30)> <<b::bitstring, a::bitstring>>
<<192>>

why can't I specify a size for a bitstring? It seems like that could be useful for catching unexpected sizes.

And why is the error message so unhelpful?

2 Answers

See the Types section of the <<>> docs:

When no type is specified, the default is integer

<<a::size(7)>> is equivalent to <<a::integer-size(7)>>, which is incorrect, because a is a bitstring, not an integer. The correct syntax is <<a::bitstring-size(7)>> (or <<a::bits-size(7)>> for brevity).

For your example:

iex> <<b::bits-size(1), a::bits-size(7)>>
<<192>>

Another example:

iex> <<prefix::bits-size(4), _::bits>> = <<0b11110000>>
<<240>>
iex> <<_::bits-size(4), suffix::bits-size(4)>> = <<0b00001111>>
<<15>>
iex> <<prefix::bits-size(4), suffix::bits-size(4)>>
<<255>>

But as Aleksei said, working with integers, rather than bitstrings, is simpler in general:

iex> <<1::1, 64::7>>
<<192>>

why is the error message so unhelpful?

Probably due to limitations in OTP in producing friendly error messages, and because nobody focussed on improving it until recently. As sabiwara mentioned in the comments, this is improved from OTP 25 and Elixir 1.14:

Improved and more detailed error messages when binary construction with the binary syntax fails.

and

These improvements apply to Elixir as well. Before v1.14, errors when constructing binaries would often be hard-to-debug generic "argument errors"

You are trying to apply ::size() constraint onto binaries, which would not obviously work (without an explicit conversion as in the last snippet.) Instead, you should apply them to binaries content.

iex||1 ▸ <<a::7>> = <<64::size(7)>>
<<64::size(7)>>
iex||2 ▸ <<b::1>> = <<1::size(1)>> 
<<1::size(1)>>
iex||3 ▸ <<a::7, b::1>>
<<129>>
iex||4 ▸ <<b::1, a::7>>      
<<192>>
Related