What is going on with this interpolated module attribute?

I was just watching a conference video on pubsub and there was a module attribute defined with interpolation and passed as the topic id. Is there something magical going on here? How is the chat_id interpolated into the string from the module attribute at runtime? I thought they were meant to serve more as compile-time constants.

I’m curious about this question, too.

Just created a small demo:

defmodule Demo do
  @topic "chat:#{chat_id}"

  def debug() do

It shows:

$> iex test.ex
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

warning: variable "chat_id" does not exist and is being expanded to "chat_id()", please use parentheses to remove the ambiguity or change the variable name
  test.ex:2: Demo

** (CompileError) test.ex:2: undefined function chat_id/0 (there is no such import)
    (elixir 1.13.4) expanding macro: Kernel.to_string/1
    test.ex:2: Demo (module)
    (elixir 1.13.4) expanding macro: Kernel.@/1
    test.ex:2: Demo (module)

Even if there was a version of Elixir where that #{ } didn’t evaluate until later it still wouldn’t make any sense - there’s no chat_id binding in scope inside mount!

This feels like a “well the ACTUAL implementation won’t fit on a slide” situation.