Is there an equivalent to a "private constructor" for elixir structs?

There is no solution which covers all use cases, but a pure Elixir validation is pretty simple to write, for example with code below:

defmodule Example do
  @required_keys ~w[sample]a
  @enforce_keys @required_keys
  defstruct [:sample]

  def sample(data) when is_list(data) do
    @required_keys
    |> Enum.reject(&Keyword.has_key?(data, &1))
    |> sample(data)
  end

  defp sample([], data) do
    case validate(data) do
      {:error, reason} -> {:error, reason}
      data -> struct(__MODULE__, data)
    end
  end

  defp sample(missing_fields, _data) do
    {:error, "missing fields: " <> Enum.join(missing_fields, ", ")}
  end

  def sample!(data) when is_list(data) do
    case validate(data) do
      {:error, reason} -> raise reason
      data -> struct!(__MODULE__, data)
    end
  end

  defp validate(data) do
    Enum.reduce_while(data, %{}, fn {key, value}, acc ->
      case validate(key, value) do
        # when value is validated put it using key
        {:ok, value} -> {:cont, Map.put(acc, key, value)}
        # otherwise when validation fails return error 
        {:error, reason} -> {:halt, {:error, reason}}
      end
    end)
  end

  defp validate(:sample, value) when is_integer(value), do: {:ok, value}
  defp validate(:sample, _value), do: {:error, "sample is not an integer"}
  defp validate(_key, value), do: {:ok, value}
end

the struct would not be validated (except @enforce_keys when using it “by hand” like %Example{}. Officially there is no support for preventing others to use write struct by hand.