mercredi 18 septembre 2019

The design pattern behind the SNAFU library

I've been playing around with the interesting SNAFU library.

A slightly modified and stand-alone example from the SNAFU page is as below:

use snafu::{ResultExt, Snafu};
use std::{fs, io, path::PathBuf};

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Unable to read configuration from {}: {}", path.display(), source))]
    ReadConfiguration { source: io::Error, path: PathBuf },
}

type Result<T, E = Error> = std::result::Result<T, E>;

fn process_data() -> Result<()> {
    let path = "config.toml";
    let read_result: std::result::Result<String, io::Error> = fs::read_to_string(path);    
    let _configuration = read_result.context(ReadConfiguration { path })?;
    Ok(())
}

fn main() {
    let foo = process_data();

    match foo {
        Err(e) => println!("Hello {}", e),
        _ => println!("success")
    }
}

The change I've made is to make the type on the Result from fs::read_to_string(path) explicit in process_data().

Given this, I can't understand how read_result has the context method available to it, as the std::result::Result docs don't make any reference to context (and the compiler similarly complains if you strip out the SNAFU stuff and try to access context).

There is a pattern being used here that is not obvious to me. My naive understanding is that external types cannot be extended because of the orphan rules, but something is happening here that looks much like such an extension.

I'm also confused by the type Result... line. I'm aware of type aliasing, but not using the syntax in which the left hand side has a generic assigned. Clearly, this is an important part of the design pattern.

My request is for clarification as to what pattern is being used here and how it works. It seems to get at some pretty interesting aspects of Rust. Further reading would be valued!

Aucun commentaire:

Enregistrer un commentaire