Validating constructor pattern?

Is there a convention/idiom for defining structs that require validation on construction?

I notice the standard library types Date and Time define a new function that returns an error tuple when the inputs are invalid:

iex> Date.new(0,0,0)
{:error, :invalid_date}

Is there any downside to declaring structs as ecto embedded_schema with a new/1 function that returns an error tuple? eg:

defmodule MyStruct do
  use Ecto.Schema
  alias Ecto.Changeset

  @primary_key false
  embedded_schema do 
    field :a, :integer
    field :b, :string
  end

  def new(attrs) do
    case cs = changeset(%MyStruct{}, attrs) do
      %{valid?: true} -> {:ok, Changeset.apply_changes(cs)}
      _ -> {:error, changeset_errors(cs)}
    end
  end

  defp changeset(data = %MyStruct{}, attrs) do
    data
    |> Changeset.cast(attrs, [:a, :b])
    |> Changset.validate_required([:a, :b])
    |> Changeset.validate_format(:b, ~r/@/)
  end

  defp changeset_errors(changeset) do
    Changeset.traverse_errors(changeset, fn {msg, opts} ->
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)
  end
end