IO.gets() behaves differently in .ex vs .exs files?

Hello, I am pretty new to Elixir. I am noticing that when I run String.trim() against the output of IO.gets() in iex, I get an error, supposedly because String.trim() cannot trim a binary string.

test.ex

defmodule Test do
  def test do
    IO.puts(String.trim(IO.gets("write something: ")))
  end
end
iex(1)> c "Test.ex"
[Test]
iex(2)> Test.test
write something: hello
** (FunctionClauseError) no function clause matching in String.trim/1    
    
    The following arguments were given to String.trim/1:
    
        # 1
        ~c"hello\n"
    
    Attempted function clauses (showing 1 out of 1):
    
        def trim(string) when is_binary(string)
    
    (elixir 1.15.4) lib/string.ex:1270: String.trim/1
    Test.ex:3: Test.test/0
    iex:2: (file)

But when I run the same logic in an .exs file, I see no error.

test.exs

IO.puts(String.trim(IO.gets("write something: ")))
% elixir test.exs
write something: hello
hello

Could someone explain this discrepancy? In case it helps, here is the output of elixir -v

% elixir -v      
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Elixir 1.15.4 (compiled with Erlang/OTP 26)

It’s the other way round. It can trim a utf-8 encoded binaries (what is called a string in elixir), but it can’t deal with charlists or iolists, which can also be returned from IO.gets.

I don’t know about any conditions, which would make it return one over the other though.

I noticed something similar recently with IO.stream. In 1.14.5-otp-26 it produces binaries, while in 1.15.4-otp-26 I get charlists.

EDIT: This only seems to be happening in iex.

Fixed in main and v1.15 branches.

12 Likes

Jose, you’re next level: fixing bugs that weren’t even reported to Elixir.

2 Likes

It looks like I’m running into this as well. What are the steps to updating my local environment? Running brew install elixir and brew reinstall elixir say I’m already up-to-date.

brew update?

A fix has not been released yet, soon.

1 Like

Ah, I misunderstood (thought you had meant that it was fixed in (all) 1.15)

So, I found this thread and am running into a similar issue.

I have an exs script that requires the user to paste in a large string, and my observation is that when I use IO.gets/1 inside a script file, the size the paste it will accept is capped at around 1024 characters, but when I run IO.gets/1 inside an iex session I can, in fact, paste a larger string.

I recorded a short demo of what I am seeing. After the recording also verified I see this in fish as well as bash just to remove those varients from consideration.

Welcome any feedback people might have.

This behavior seems to be a limitation of the shell you’re using. In my test setup (Elixir 1.15/OTP 26/Bash/Ubuntu), IO.gets/1 has a limit of 4096 characters.

This is consistent with using the read bult-in command which cuts off input at the same length in my environment.
This can be changed with the following shell command that changes the shell’s input behavior: stty -icanon. After you run this, both read and IO.gets/2 from an exs script can accept longer input.

If anyone else is curious, here’s a script to try this for yourselves.

input = IO.gets("Input your string ... ")

{type, length} =
  cond do
    is_binary(input) -> {"string", String.length(input)}
    is_list(input) -> {"charlist", length(input)}
  end

IO.puts("The input had a length of #{length}. It was a #{type}.")