代码之家  ›  专栏  ›  技术社区  ›  JasonG

在elixir中使用声明结构中的原子对repl有效,但在应用程序中无效

  •  1
  • JasonG  · 技术社区  · 7 年前

    我想用字符串。使用elixir中的现有原子来避免内存泄漏。

    这100%适用于REPL:

    iex(1)> defmodule MyModule do
    ...(1)> defstruct my_crazy_atom: nil
    ...(1)> end
    {:module, MyModule,
     <<70, 79, 82, ...>>,
     %MyModule{my_crazy_atom: nil}}
    

    现在原子 my_crazy_atom

    iex(2)> String.to_existing_atom "my_crazy_atom"
    :my_crazy_atom
    

    与之相比:

    iex(3)> String.to_existing_atom "my_crazy_atom2"
    ** (ArgumentError) argument error
        :erlang.binary_to_existing_atom("my_crazy_atom2", :utf8)
    

    但我有一些代码如下所示:

    defmodule Broadcast.Config.File do
      defstruct channel_id: nil, parser: nil
    end
    

    我可以用毒药破译

    keys: :atoms! 
    

    甚至只是打电话

    String.to_existing_atom("parser")
    

    在代码的同一个地方,我得到一个错误:

    ** (Mix) Could not start application broadcast: exited in: 
    Broadcast.Application.start(:normal, [])
        ** (EXIT) an exception was raised:
            ** (ArgumentError) argument error
                :erlang.binary_to_existing_atom("parser", :utf8)
    

    奇怪的是,如果我实例化结构并检查它,那么问题就会消失!

    IO.puts inspect %Broadcast.Config.File{}
    String.to_existing_atom("parser")
    

    这是怎么回事?这是订购的东西吗?

    2 回复  |  直到 7 年前
        1
  •  4
  •   Dogbert    7 年前

    这是因为Elixir默认情况下从编译后的文件中延迟加载模块。梁文件首次使用时。(如果 start_permanent 设置为 true mix.exs ,在 :prod 环境,因为Elixir急切地加载包的所有模块。)

    :my_crazy_atom Blah Foo . 如果启动REPL会话并运行 Foo.to_existing_atom 废话 模块是 String.to_existing_atom("my_crazy_atom") 失败。

    # Credits: @mudasobwa
    defmodule Blah do
      defstruct my_crazy_atom: nil
    end
    
    defmodule Foo do
      def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
    end
    

    正如您所观察到的,如果手动创建一次结构,则所有后续调用 返回正确的原子。这是因为当您创建结构时,Elixir将加载。该模块的光束文件,也将加载该模块使用的所有原子。

    加载模块(与创建结构相比)的更好方法是使用 Code.ensure_loaded/1 加载模块:

    {:module, _} = Code.ensure_loaded(Blah)
    
        2
  •  1
  •   Aleksei Matiushkin    7 年前

    编译过程立即发生

    iex(1)> defmodule MyModule do
    ...(1)>   defstruct my_crazy_atom: nil
    ...(1)> end
    

    在您的应用程序OTOH中,正在执行编译过程 提前 VM的不同调用 . 因此,除非明确使用该结构,否则不会创建原子。

    可以将其视为OOP中的类声明与实例化:类定义的存在并不保证存在此类的实例。

    IO.puts "I AM HERE" 在模块声明内,紧接之前 defstruct . 在REPL中,您将看到这一行立即打印。在应用程序中,您将在编译过程中看到它,而在正常运行应用程序时不会看到它。


    复制步骤:

    $ mix new blah && cd blah
    $ cat lib/blah.ex
    defmodule Blah do
      defstruct my_crazy_atom: nil
    end
    
    defmodule Foo do
      def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
    end
    $ mix compile
    $ iex -S mix
    iex|1 ▶ Foo.to_existing_atom
    ** (ArgumentError) argument error
        :erlang.binary_to_existing_atom("my_crazy_atom", :utf8)
        (blah) lib/blah.ex:6: Foo.to_existing_atom/0
    iex|1 ▶ %Blah{}
    %Blah{my_crazy_atom: nil}
    iex|2 ▶ Foo.to_existing_atom
    :my_crazy_atom
    

    为了全面起见,我将把它放在这里 this brilliant answer

    defmodule AtomLookUp do
      defp atom_by_number(n),
        do: :erlang.binary_to_term(<<131, 75, n::24>>)
    
      def atoms(n \\ 0) do
        try do
          [atom_by_number(n) | atoms(n + 1)]
        rescue
          _ -> []
        end
      end
      def atom?(value) when is_binary(value) do
        result = atoms()
                 |> Enum.map(&Atom.to_string/1)
                 |> Enum.find(& &1 == value)
        if result, do: String.to_existing_atom(result)
      end
    end
    
    iex|1 ▶ AtomLookUp.atom? "my_crazy_atom"
    nil
    iex|2 ▶ %Blah{}
    %Blah{my_crazy_atom: nil}
    iex|3 ▶ AtomLookUp.atom? "my_crazy_atom"
    :my_crazy_atom