ferrule documentation[styled mode]
specrfcshome

RFC-0001

error transformation

Status: draft | Target: α2


RFC-0001: error transformation

summary

map_error provides syntax for transforming error types at call sites, enabling clean error domain transitions without boilerplate match statements.

motivation

currently, converting between error types requires verbose matching:

function read_config(path: string) -> Result<Config, ConfigError>
  effects [Fs]
  errors [ConfigError]
{
  const content = match fs.read(path) {
    Ok(c) => c,
    Err(e) => return err(ConfigError.IoFailed(e.to_string())),
  };

  const parsed = match parse_toml(content) {
    Ok(p) => p,
    Err(e) => return err(ConfigError.ParseFailed(e.to_string())),
  };

  return ok(parsed);
}

with map_error:

function read_config(path: string) -> Result<Config, ConfigError>
  effects [Fs]
  errors [ConfigError]
{
  const content = fs.read(path)
    .map_error(|e| ConfigError.IoFailed(e.to_string()))?;

  const parsed = parse_toml(content)
    .map_error(|e| ConfigError.ParseFailed(e.to_string()))?;

  return ok(parsed);
}

detailed design

syntax

map_error is a method on result types:

result.map_error(transform_fn)

where transform_fn is function(E1) -> E2.

chaining with propagation

map_error composes with ?:

const value = fallible_call()
  .map_error(|e| NewError.from(e))?;

type signature

impl<T, E> Result<T, E> {
  function map_error<F>(self, f: function(E) -> F) -> Result<T, F> {
    match self {
      Ok(t) => Ok(t),
      Err(e) => Err(f(e)),
    }
  }
}

domain unification

when combining errors from different sources:

error NetworkError = Timeout | ConnectionFailed;
error ParseError = InvalidJson | InvalidXml;
error ApiError = Network(NetworkError) | Parse(ParseError);

function fetch_and_parse(url: string) -> Result<Data, ApiError>
  effects [Net]
  errors [ApiError]
{
  const response = http.get(url)
    .map_error(|e| ApiError.Network(e))?;

  const data = parse(response.body)
    .map_error(|e| ApiError.Parse(e))?;

  return ok(data);
}

shorthand with From

if an error type implements From, automatic conversion is available:

impl From<NetworkError> for ApiError {
  function from(e: NetworkError) -> ApiError {
    return ApiError.Network(e);
  }
}

// then this works automatically:
const response = http.get(url)?;  // auto-converts NetworkError to ApiError

drawbacks

alternatives

try-with syntax

const value = try http.get(url) with |e| ApiError.Network(e);

rejected for being too different from the rest of the language.

only explicit match

keep error conversion explicit with match statements.

rejected because it creates too much boilerplate.

prior art

languagefeature
rustmap_err, From trait, ? operator
swiftmapError on Result
kotlinmapFailure on Result

unresolved questions

  1. should From conversion be implicit or require explicit opt-in?
  2. how does this interact with context frames?
  3. should there be a map_ok for symmetry?

future possibilities