How to avoid “protocol String.Chars not implemented”?

I wanna preface this by saying, I like Elixir, like really like it, I have been writing Elixir with Phoenix fulltime for over six months now and I am getting the hand of quite a few things, but some annoyances are just so so so bad (to me), I just can’t get over them.

So I will be unfair in the following paragraphs and just rant, it’s not Elixir, it’s me and my lack of skill, and experience, so I would love if folks would share how they do things, and their tooling and workflow setup, so my life can improve.


Why the heck, does a “dynamic, functional language for building scalable and maintainable applications.” crash so often with ** (Protocol.UndefinedError) protocol String.Chars not implemented for %.....

Seriously, what the heck, just give me [object Object] or whatever, I don’t care, fix this, it is annoying, I just wanna add log states, and not worry about the exact specs of things, either the tooling should tell me, this is wrong, or just make it work, I can’t even tell you, how many times, I have had this, while I am trying to debug a specific thing in prod, add logs, and then this, so JUST FIX THISSSSS, this is garbage

If you are dynamic language, why the heck are you so anal about this, let me rollllll, or fail at build or something, just don’t suck at runtime.

And on that note, how the heck so I make vscode work better, my codebase isn’t even that huge, and **** is just broken, the language server (ElixirLS) is so bad, I know we have Expert, but I not been able to get the setup right to be able to play with it.

If you use inspect you should have an improved quality of life, e.g. Log.info(“#{inspect object}”)

4 Likes

Let it crash, my son.

I do, but I actually need to fix the problem, the real problem, and I can’t find it with this behaviour

How this info will help you in production?

Do you write tests for your application?

1 Like

One of the great lessons we employ in business is to fire terrible customers. The malcontents, the complainers, the ones who won’t put in the work but complain endlessly. The bottomless pits that can never be satisfied.

@vipulbhj - maybe you would be happier in another programming community. Maybe Rust or JS would be more to your liking.

It won’t, but this detail is inconsequential to me, I can fix this hole later, once I have the entire flow, I can decide if this is even something I need or just remove this, as I said, these are just temporary logs I put in, to debug a specific flow, working in dev, but fails in prod, this specific case, I had a variable holding on a term() , ecto record, and I just wrote it in a logger.info to just give me more context while reading my logs.

I like Elixir, and thus I complain :smiley: , only out of love, and hope of learning something new

1 Like

Personally, I find the “[object Object]” in JS infuriating since it’s completely useless information. Just recurse through the object and give me the properties. That being said, the reason why you are getting those protocol errors is because Elixir is a dynamically typed but also strongly typed language. It’s similar to Python in this regard. In Python, you can assign any value to any variable (dynamic typing), but if you try to add an int and a list together, it will crash. Javascript on the other hand is both dynamically and weakly typed, so it will cast types around willy nilly without telling you that you’ve just created a logical bug.

I would take advantage of the type errors an research them a bit to see what’s going on. If you’re just trying to print something out with IO.puts, you can use IO.inspect instead, and you shouldn’t see the string protocol error. If you’re actually trying to convert a string somewhere and seeing that error, Elixir is preventing a runtime crash. :slight_smile:

I would also recommend looking into Observer and the Debug Adapter Protocol for additional debugging capabilities. Elixir has the best runtime observability of any language ecosystem I’ve used, but it takes some getting used to if you’re coming from a language where printf debugging is the best tool in the tool box.

As for the LSP, I feel your pain there. I have configuration in my neovim init.lua for all three elixir LSPs (ElixirLS, Lexical, and Expert) because they all stop working periodically, but at least one will usually be working okay. One thing you can try is to delete the .elixir_ls directory from your project. If you’ve updated or installed new deps, or switched elixir versions with asdj or mise, or just sneezed too loud near your laptop, ElixirLS will sometimes get some junk in its build cache that just needs to get deleted and rebuilt from scratch.

With all that being said. Try to be patient with the LSP stuff though. The core team has dedicated some resources to Expert in order to improve things, but Elixir is developed by a very small team with very limited resources compared to the closest competing languages (Typescript and Go), so it’s not going to be as good right away, and will take time to get better, but they are working on it, and I for one really appreciate the effort all the volunteers are putting in. :slight_smile:

Hope that helps a bit. Cheers!

3 Likes

Thanks, Observer and the Debug Adapter Protocol is the kind of thing, I was hoping someone would point me to, would read up :smiley:

I don’t recall this happening very often in the code I write and in the code I used to write when I was learning the language. Perhaps you should try to use inspect (and IO.inspect respectively) when you want to inspect data instead of interpolation or IO.puts or to_string?

1 Like

Also, rename the topic title please to reflect the subject

I don’t know why, but something is me already adds inspect when to string interpolations I do for errors, should make an habit to use it at all the places.

Any suggestions ?

Rule of thumb: if this string is used for debugging, use inspect

How to avoid “protocol String.Chars not implemented”?

2 Likes

Quite fascinating what people find difficult about Elixir. :open_mouth:

Just use inspect and move on with life.

1 Like

Here is the wrong way to do it, as requested:

lib/your_struct.ex

defmodule YourStruct do
  defstruct [:hello, :world]
end

defimpl String.Chars, for: YourStruct do
  def to_string(%YourStruct{} = struct) do
    inspect(struct)
  end
end
iex> x = %YourStruct{hello: "abc", world: 123}
%YourStruct{hello: "abc", world: 123}

iex> "#{x}"
"%YourStruct{hello: \"abc\", world: 123}"

If you want to customize this, just modify to_string/1 as desired.

How to avoid “protocol String.Chars not implemented”?

Don’t try to use a struct where a string is expected.

If you are dynamic language, why the heck are you so anal about this, let me rollllll, or fail at build or something, just don’t suck at runtime.

It’s also strong, which is pretty rare. That’s why you can’t do "1" + 1. It’s not going to automatically convert one type to another - it’s up to you to say how. It’s also why you can’t to_string a struct. How to convert a struct to a string depends on the situation, so it can’t be universally implemented in String.Chars.

There’s a few pieces here, some already mentioned by others.

It all starts with elixir being indeed a dynamicly typed language (vs. statically typed), but it’s still strongly typed (no implicit type conversion). Those are two different axis of defining type support.

So to start out you’d need to convert anything to a string before trying to concatinate with another string. But elixir has protocol to define polymorphism, where many distinct datatypes can implement a shared interface.

String.Chars is one of those. Its purpose is to convert types to a string format where applicable – turn the information represented by the data into a string representing the same or at least strongly related information. This is explicitly not the protocol for “debugging information”. For that there’s the Inspect protocol. Contrary to String.Chars, the Inspect protocol works for all data even without explicit implementation due to having a fallback implementation, a useful property for an interface meant to deal with any kind of data in a debugging context.

So given your introduction on this thread it looks like you’re looking for the latter protocol rather than the former. You can use inspect or IO.inspect to have the Inspect protocol be used.

While the elixir typesystem as a whole is still work in progress this specific feature exists in 1.19. See Elixir v1.19 released: enhanced type checking and up to 4x faster compilation for large projects - The Elixir programming language for an example in the release blog post.

Without a static typesystem to support stuff like that runtime error is simply the best that the language can do. Sure it could just support protocols to always support all data, but that would undermine purposeful implementation of protocols. In most cases just a subset of possible data makes sense to implement some interface, while fallbacks only sometimes make sense without increasing the potential for bugs to a greater degree than the usefulness of the protocol would bring in the first place.

3 Likes