error transformation
drafttarget: α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 ApiErrordrawbacks
- adds complexity to error handling
- implicit conversion with
Fromcan obscure error origins - method chaining style differs from other ferrule idioms
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
| language | feature |
|---|---|
| rust | map_err, From trait, ? operator |
| swift | mapError on Result |
| kotlin | mapFailure on Result |
unresolved questions
- should
Fromconversion be implicit or require explicit opt-in? - how does this interact with context frames?
- should there be a
map_okfor symmetry?
future possibilities
and_thenfor chaining fallible operationsor_elsefor error recovery- context accumulation across
map_errorchains