Going back to std

Not using chainerror and going full std would look like this:

Btw, the code size is bigger than using chainerror :-)

#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]

pub mod mycrate {
    use std::error::Error as StdError;

    use self::func2mod::{do_some_io, func2};

    pub mod func2mod {
        use std::error::Error as StdError;
        use std::io;

        pub enum ErrorKind {
            IO(String),
        }

        impl std::fmt::Display for ErrorKind {
            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
                match self {
                    ErrorKind::IO(s) => std::fmt::Display::fmt(s, f),
                }
            }
        }

        impl std::fmt::Debug for ErrorKind {
            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
                match self {
                    ErrorKind::IO(s) => std::fmt::Display::fmt(s, f),
                }
            }
        }

        macro_rules! mcontext {
            ( $k:expr ) => {{
                |e| {
                    Error(
                        $k,
                        Some(Box::from(e)),
                        Some(concat!(file!(), ":", line!(), ": ")),
                    )
                }
            }};
        }

        pub struct Error(
            ErrorKind,
            Option<Box<dyn std::error::Error + 'static>>,
            Option<&'static str>,
        );

        impl Error {
            pub fn kind(&self) -> &ErrorKind {
                &self.0
            }
        }

        impl From<ErrorKind> for Error {
            fn from(e: ErrorKind) -> Self {
                Error(e, None, None)
            }
        }

        impl std::error::Error for Error {
            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
                self.1.as_ref().map(|e| e.as_ref())
            }
        }

        impl std::fmt::Display for Error {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                std::fmt::Display::fmt(&self.0, f)
            }
        }

        impl std::fmt::Debug for Error {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                if let Some(ref o) = self.2 {
                    std::fmt::Display::fmt(o, f)?;
                }

                std::fmt::Debug::fmt(&self.0, f)?;

                if let Some(e) = self.source() {
                    std::fmt::Display::fmt("\nCaused by:\n", f)?;
                    std::fmt::Debug::fmt(&e, f)?;
                }
                Ok(())
            }
        }

        pub fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error>> {
            Err(io::Error::from(io::ErrorKind::NotFound))?;
            Ok(())
        }

        pub fn func2() -> std::result::Result<(), Error> {
            let filename = "foo.txt";
            do_some_io().map_err(mcontext!(ErrorKind::IO(format!(
                "Error reading '{}'",
                filename
            ))))?;
            Ok(())
        }
    }

    #[derive(Debug)]
    pub enum ErrorKind {
        Func2,
        IO(String),
    }

    impl std::fmt::Display for ErrorKind {
        fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
            match self {
                ErrorKind::Func2 => write!(f, "func1 error calling func2"),
                ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename),
            }
        }
    }

    macro_rules! mcontext {
        ( $k:expr ) => {{
            |e| {
                Error(
                    $k,
                    Some(Box::from(e)),
                    Some(concat!(file!(), ":", line!(), ": ")),
                )
            }
        }};
    }

    pub struct Error(
        ErrorKind,
        Option<Box<dyn std::error::Error + 'static>>,
        Option<&'static str>,
    );

    impl Error {
        pub fn kind(&self) -> &ErrorKind {
            &self.0
        }
    }

    impl From<ErrorKind> for Error {
        fn from(e: ErrorKind) -> Self {
            Error(e, None, None)
        }
    }

    impl std::error::Error for Error {
        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
            self.1.as_ref().map(|e| e.as_ref())
        }
    }

    impl std::fmt::Display for Error {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            std::fmt::Display::fmt(&self.0, f)
        }
    }

    impl std::fmt::Debug for Error {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            if let Some(ref o) = self.2 {
                std::fmt::Display::fmt(o, f)?;
            }

            std::fmt::Debug::fmt(&self.0, f)?;
            if let Some(e) = self.source() {
                std::fmt::Display::fmt("\nCaused by:\n", f)?;
                std::fmt::Debug::fmt(&e, f)?;
            }
            Ok(())
        }
    }

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

    pub fn func1() -> Result<()> {
        func2().map_err(mcontext!(ErrorKind::Func2))?;
        let filename = String::from("bar.txt");
        do_some_io().map_err(mcontext!(ErrorKind::IO(filename)))?;
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    use mycrate::func1;
    use mycrate::ErrorKind;
    use std::error::Error;
    use std::io;

    if let Err(e) = func1() {
        match e.kind() {
            ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
            ErrorKind::IO(ref filename) => {
                eprintln!("Main Error Report: func1 error reading '{}'", filename)
            }
        }

        eprintln!();
        let mut s: &dyn Error = &e;
        while let Some(c) = s.source() {
            if let Some(ioerror) = c.downcast_ref::<io::Error>() {
                eprintln!("caused by: std::io::Error: {}", ioerror);
                match ioerror.kind() {
                    io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"),
                    _ => {}
                }
            } else {
                eprintln!("caused by: {}", c);
            }
            s = c;
        }

        eprintln!("\nDebug Error:\n{:?}", e);

        std::process::exit(1);
    }
    Ok(())
}