Wanting to know more about the "Elixir" way of doing things

I think your protocols version is great, but you should implement all the functions:

def perimeter(%Circle{radius: radius}) do
  2 * :math.pi() * radius
end

It seems that even structs that don’t implement all the functions in a protocol have dummy placeholders. Here’s an example from the core library for Function, which doesn’t implement all the functions of Enumerable:

https://github.com/elixir-lang/elixir/blob/6730d669fb319411f8e411d4126f1f4067ef9231/lib/elixir/lib/enum.ex#L4778-L4780