Repository: mun-lang/mun (language: rust)

Issue:
Remove deprecated `Failure` crate
We use the `Failure` crate throughout the Mun codebase but it has been deprecated. It would be a good idea to replace it.

Suggested alternatives to use in places where we do use `Failure` (which is not really a lot) are:

* [anyhow](https://github.com/dtolnay/anyhow) as a replacement for `failure::Error`
* ~~[displaydoc](https://github.com/yaahc/displaydoc) seems like a good replacement for `#[derive(Fail)]`~~
* [thiserror](https://github.com/dtolnay/thiserror) as a replacement for `#[derive(Fail)]`

**Good first issue process:**

- [x] [read our contributing guidelines](https://github.com/mun-lang/mun/blob/master/CONTRIBUTING.md)
- [x] claim this issue (assign yourself or comment below)
- [x] implement the feature and start a Pull Request with in the description: closes #177. 

If this is your first PR, welcome 🎉 😄



Discussion / hints:
I came across this [article](https://nick.groenen.me/posts/rust-error-handling) about error handling in Rust. They combine anyhow with [thiserror](https://crates.io/crates/thiserror), which does the same as `derive(Failure)` except that they are by the same maintainer. I'd prefer that approach as they were specifically designed as two pieces of a puzzle.
I actually preferred `displaydoc` over `thiserror` (although you still need `thiserror` when using `displaydoc`) because I really like that it also immediately is added as a comment.

However! reading the article linked by @Wodann also shows examples where you might not want the comment to say the same thing as the error message. So just using `thiserror` indeed seems like a better option.

Fix patch:
diff --git a/crates/mun/Cargo.toml b/crates/mun/Cargo.toml
index 69766fa14..5ad212396 100644
--- a/crates/mun/Cargo.toml
+++ b/crates/mun/Cargo.toml
@@ -14,7 +14,7 @@ categories = ["command-line-interface", "game-development", "mun"]
 default-run = "mun"
 
 [dependencies]
-failure = "0.1.7"
+anyhow = "1.0.31"
 clap = "2.33.0"
 log = "0.4"
 pretty_env_logger = "0.4"
diff --git a/crates/mun/src/main.rs b/crates/mun/src/main.rs
index da5ecf17e..142cf5ff0 100644
--- a/crates/mun/src/main.rs
+++ b/crates/mun/src/main.rs
@@ -1,21 +1,19 @@
-#[macro_use]
-extern crate failure;
-
 use std::cell::RefCell;
 use std::env;
 use std::rc::Rc;
 use std::time::Duration;
 
+use anyhow::anyhow;
 use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
 use mun_compiler::{Config, DisplayColor, PathOrInline, Target};
 use mun_runtime::{invoke_fn, ReturnTypeReflection, Runtime, RuntimeBuilder};
 
-fn setup_logging() -> Result<(), failure::Error> {
+fn setup_logging() -> Result<(), anyhow::Error> {
     pretty_env_logger::try_init()?;
     Ok(())
 }
 
-fn main() -> Result<(), failure::Error> {
+fn main() -> Result<(), anyhow::Error> {
     setup_logging()?;
     let matches = App::new("mun")
         .version(env!("CARGO_PKG_VERSION"))
@@ -93,7 +91,7 @@ fn main() -> Result<(), failure::Error> {
 }
 
 /// Build the source file specified
-fn build(matches: &ArgMatches) -> Result<(), failure::Error> {
+fn build(matches: &ArgMatches) -> Result<(), anyhow::Error> {
     let options = compiler_options(matches)?;
     if matches.is_present("watch") {
         mun_compiler_daemon::main(options)
@@ -103,7 +101,7 @@ fn build(matches: &ArgMatches) -> Result<(), failure::Error> {
 }
 
 /// Starts the runtime with the specified library and invokes function `entry`.
-fn start(matches: &ArgMatches) -> Result<(), failure::Error> {
+fn start(matches: &ArgMatches) -> Result<(), anyhow::Error> {
     let runtime = runtime(matches)?;
 
     let borrowed = runtime.borrow();
@@ -120,40 +118,37 @@ fn start(matches: &ArgMatches) -> Result<(), failure::Error> {
     if let Some(ret_type) = fn_definition.prototype.signature.return_type() {
         let type_guid = &ret_type.guid;
         if *type_guid == bool::type_guid() {
-            let result: bool =
-                invoke_fn!(runtime, entry_point).map_err(|e| failure::err_msg(format!("{}", e)))?;
+            let result: bool = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
 
             println!("{}", result)
         } else if *type_guid == f64::type_guid() {
-            let result: f64 =
-                invoke_fn!(runtime, entry_point).map_err(|e| failure::err_msg(format!("{}", e)))?;
+            let result: f64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
 
             println!("{}", result)
         } else if *type_guid == i64::type_guid() {
-            let result: i64 =
-                invoke_fn!(runtime, entry_point).map_err(|e| failure::err_msg(format!("{}", e)))?;
+            let result: i64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;
 
             println!("{}", result)
         } else {
-            return Err(failure::err_msg(format!(
+            return Err(anyhow!(
                 "Only native Mun return types are supported for entry points. Found: {}",
                 ret_type.name()
-            )));
+            ));
         };
         Ok(())
     } else {
         #[allow(clippy::unit_arg)]
-        invoke_fn!(runtime, entry_point).map_err(|e| failure::err_msg(format!("{}", e)))
+        invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))
     }
 }
 
-fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOptions, failure::Error> {
+fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOptions, anyhow::Error> {
     let optimization_lvl = match matches.value_of("opt-level") {
         Some("0") => mun_compiler::OptimizationLevel::None,
         Some("1") => mun_compiler::OptimizationLevel::Less,
         None | Some("2") => mun_compiler::OptimizationLevel::Default,
         Some("3") => mun_compiler::OptimizationLevel::Aggressive,
-        _ => return Err(format_err!("Only optimization levels 0-3 are supported")),
+        _ => return Err(anyhow!("Only optimization levels 0-3 are supported")),
     };
 
     let display_color = matches
@@ -180,7 +175,7 @@ fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOption
     })
 }
 
-fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, failure::Error> {
+fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error> {
     let builder = RuntimeBuilder::new(
         matches.value_of("LIBRARY").unwrap(), // Safe because its a required arg
     );
@@ -195,6 +190,6 @@ fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, failure::Error>
     builder.spawn()
 }
 
-fn language_server(_matches: &ArgMatches) -> Result<(), failure::Error> {
-    mun_language_server::run_server().map_err(|e| failure::format_err!("{}", e))
+fn language_server(_matches: &ArgMatches) -> Result<(), anyhow::Error> {
+    mun_language_server::run_server().map_err(|e| anyhow!("{}", e))
 }
diff --git a/crates/mun_codegen/Cargo.toml b/crates/mun_codegen/Cargo.toml
index 945cc1e33..8698f1c6b 100644
--- a/crates/mun_codegen/Cargo.toml
+++ b/crates/mun_codegen/Cargo.toml
@@ -18,7 +18,8 @@ hir = { version = "=0.2.0", path = "../mun_hir", package = "mun_hir" }
 mun_codegen_macros = { path = "../mun_codegen_macros", package = "mun_codegen_macros" }
 mun_target = { version = "=0.2.0", path = "../mun_target" }
 mun_lld = { version = "=70.2.0", path = "../mun_lld" }
-failure = "0.1.7"
+anyhow = "1.0.31"
+thiserror = "1.0.19"
 salsa="0.12"
 md5="0.6.1"
 array-init="0.1.0"
diff --git a/crates/mun_codegen/src/code_gen.rs b/crates/mun_codegen/src/code_gen.rs
index a60a65428..4c959c52a 100644
--- a/crates/mun_codegen/src/code_gen.rs
+++ b/crates/mun_codegen/src/code_gen.rs
@@ -1,7 +1,6 @@
 use crate::code_gen::linker::LinkerError;
 use crate::value::{IrTypeContext, IrValueContext};
 use crate::IrDatabase;
-use failure::Fail;
 use hir::{FileId, RelativePathBuf};
 use inkwell::targets::TargetData;
 use inkwell::{
@@ -17,23 +16,24 @@ use std::{
     sync::Arc,
 };
 use tempfile::NamedTempFile;
+use thiserror::Error;
 
 mod linker;
 pub mod symbols;
 
-#[derive(Debug, Fail)]
+#[derive(Debug, Error)]
 enum CodeGenerationError {
-    #[fail(display = "{}", 0)]
-    LinkerError(#[fail(cause)] LinkerError),
-    #[fail(display = "error linking modules: {}", 0)]
+    #[error("{0}")]
+    LinkerError(#[source] LinkerError),
+    #[error("error linking modules: {0}")]
     ModuleLinkerError(String),
-    #[fail(display = "unknown target triple: {}", 0)]
+    #[error("unknown target triple: {0}")]
     UnknownTargetTriple(String),
-    #[fail(display = "error creating target machine")]
+    #[error("error creating target machine")]
     CouldNotCreateTargetMachine,
-    #[fail(display = "error creating object file")]
+    #[error("error creating object file")]
     CouldNotCreateObjectFile(io::Error),
-    #[fail(display = "error generating machine code")]
+    #[error("error generating machine code")]
     CodeGenerationError(String),
 }
 
@@ -55,7 +55,7 @@ impl ObjectFile {
         target_machine: &TargetMachine,
         src_path: RelativePathBuf,
         module: Arc<inkwell::module::Module>,
-    ) -> Result<Self, failure::Error> {
+    ) -> Result<Self, anyhow::Error> {
         let obj = target_machine
             .write_to_memory_buffer(&module, FileType::Object)
             .map_err(|e| CodeGenerationError::CodeGenerationError(e.to_string()))?;
@@ -73,7 +73,7 @@ impl ObjectFile {
         })
     }
 
-    pub fn into_shared_object(self, out_dir: Option<&Path>) -> Result<PathBuf, failure::Error> {
+    pub fn into_shared_object(self, out_dir: Option<&Path>) -> Result<PathBuf, anyhow::Error> {
         // Construct a linker for the target
         let mut linker = linker::create_with_target(&self.target);
         linker.add_object(self.obj_file.path())?;
@@ -99,7 +99,7 @@ pub struct ModuleBuilder<'a, D: IrDatabase> {
 
 impl<'a, D: IrDatabase> ModuleBuilder<'a, D> {
     /// Constructs module for the given `hir::FileId` at the specified output file location.
-    pub fn new(db: &'a D, file_id: FileId) -> Result<Self, failure::Error> {
+    pub fn new(db: &'a D, file_id: FileId) -> Result<Self, anyhow::Error> {
         let target = db.target();
 
         // Construct a module for the assembly
@@ -138,7 +138,7 @@ impl<'a, D: IrDatabase> ModuleBuilder<'a, D> {
     }
 
     /// Constructs an object file.
-    pub fn build(self) -> Result<ObjectFile, failure::Error> {
+    pub fn build(self) -> Result<ObjectFile, anyhow::Error> {
         let group_ir = self.db.group_ir(self.file_id);
         let file = self.db.file_ir(self.file_id);
 
diff --git a/crates/mun_codegen/src/code_gen/linker.rs b/crates/mun_codegen/src/code_gen/linker.rs
index 6dea17ccc..2544e3082 100644
--- a/crates/mun_codegen/src/code_gen/linker.rs
+++ b/crates/mun_codegen/src/code_gen/linker.rs
@@ -1,10 +1,10 @@
-use failure::Fail;
 use mun_target::spec;
 use mun_target::spec::LinkerFlavor;
 use std::fmt;
 use std::path::{Path, PathBuf};
+use thiserror::Error;
 
-#[derive(Fail, Debug)]
+#[derive(Error, Debug)]
 pub enum LinkerError {
     /// Error emitted by the linker
     LinkError(String),
diff --git a/crates/mun_compiler/Cargo.toml b/crates/mun_compiler/Cargo.toml
index bc8059c61..3f94b88e5 100644
--- a/crates/mun_compiler/Cargo.toml
+++ b/crates/mun_compiler/Cargo.toml
@@ -13,7 +13,7 @@ keywords = ["game", "hot-reloading", "language", "mun", "scripting"]
 categories = ["game-development", "mun"]
 
 [dependencies]
-failure = "0.1.7"
+anyhow = "1.0.31"
 mun_codegen = { version = "=0.2.0", path="../mun_codegen" }
 mun_syntax = { version = "=0.2.0", path="../mun_syntax" }
 mun_hir = { version = "=0.2.0", path="../mun_hir" }
diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs
index e204e0993..c16b8beca 100644
--- a/crates/mun_compiler/src/driver.rs
+++ b/crates/mun_compiler/src/driver.rs
@@ -53,7 +53,7 @@ impl Driver {
     pub fn with_file(
         config: Config,
         path: PathOrInline,
-    ) -> Result<(Driver, FileId), failure::Error> {
+    ) -> Result<(Driver, FileId), anyhow::Error> {
         let mut driver = Driver::with_config(config);
 
         // Construct a SourceRoot
@@ -109,10 +109,7 @@ impl Driver {
 
     /// Emits all diagnostic messages currently in the database; returns true if errors were
     /// emitted.
-    pub fn emit_diagnostics(
-        &self,
-        writer: &mut dyn std::io::Write,
-    ) -> Result<bool, failure::Error> {
+    pub fn emit_diagnostics(&self, writer: &mut dyn std::io::Write) -> Result<bool, anyhow::Error> {
         let mut has_errors = false;
         let dlf = DisplayListFormatter::new(self.display_color.should_enable(), false);
         for file_id in self.db.source_root(WORKSPACE).files() {
@@ -133,7 +130,7 @@ impl Driver {
 
 impl Driver {
     /// Generate an assembly for the given file
-    pub fn write_assembly(&mut self, file_id: FileId) -> Result<PathBuf, failure::Error> {
+    pub fn write_assembly(&mut self, file_id: FileId) -> Result<PathBuf, anyhow::Error> {
         let module_builder = ModuleBuilder::new(&self.db, file_id)?;
         let obj_file = module_builder.build()?;
         obj_file.into_shared_object(self.out_dir.as_deref())
diff --git a/crates/mun_compiler/src/lib.rs b/crates/mun_compiler/src/lib.rs
index 5069f91bd..876e8033d 100644
--- a/crates/mun_compiler/src/lib.rs
+++ b/crates/mun_compiler/src/lib.rs
@@ -57,7 +57,7 @@ impl CompilerOptions {
     }
 }
 
-pub fn main(options: CompilerOptions) -> Result<Option<PathBuf>, failure::Error> {
+pub fn main(options: CompilerOptions) -> Result<Option<PathBuf>, anyhow::Error> {
     let (mut driver, file_id) = Driver::with_file(options.config, options.input)?;
 
     if driver.emit_diagnostics(&mut stderr())? {
diff --git a/crates/mun_compiler_daemon/Cargo.toml b/crates/mun_compiler_daemon/Cargo.toml
index f5a11609a..c60160b24 100644
--- a/crates/mun_compiler_daemon/Cargo.toml
+++ b/crates/mun_compiler_daemon/Cargo.toml
@@ -13,6 +13,6 @@ keywords = ["game", "hot-reloading", "language", "mun", "scripting"]
 categories = ["game-development", "mun"]
 
 [dependencies]
-failure = "0.1.7"
+anyhow = "1.0.31"
 mun_compiler = { version = "=0.2.0", path = "../mun_compiler" }
 notify = "4.0.12"
diff --git a/crates/mun_compiler_daemon/src/lib.rs b/crates/mun_compiler_daemon/src/lib.rs
index d545df2a3..d84a7b1ac 100644
--- a/crates/mun_compiler_daemon/src/lib.rs
+++ b/crates/mun_compiler_daemon/src/lib.rs
@@ -1,7 +1,7 @@
 use std::sync::mpsc::channel;
 use std::time::Duration;
 
-use failure::Error;
+use anyhow::Error;
 use mun_compiler::{CompilerOptions, Driver, PathOrInline};
 use notify::{RecommendedWatcher, RecursiveMode, Watcher};
 
diff --git a/crates/mun_runtime/Cargo.toml b/crates/mun_runtime/Cargo.toml
index 3c43e48e5..edb230201 100644
--- a/crates/mun_runtime/Cargo.toml
+++ b/crates/mun_runtime/Cargo.toml
@@ -14,7 +14,7 @@ categories = ["game-development", "mun"]
 
 [dependencies]
 abi = { version = "=0.2.0", path = "../mun_abi", package = "mun_abi" }
-failure = "0.1.7"
+anyhow = "1.0.31"
 libloading = "0.5"
 md5 = "0.7.0"
 memory = { version = "=0.1.0", path = "../mun_memory", package = "mun_memory" }
diff --git a/crates/mun_runtime/src/assembly.rs b/crates/mun_runtime/src/assembly.rs
index 8fe8acf09..868eb7b3a 100644
--- a/crates/mun_runtime/src/assembly.rs
+++ b/crates/mun_runtime/src/assembly.rs
@@ -28,7 +28,7 @@ impl Assembly {
         library_path: &Path,
         gc: Arc<GarbageCollector>,
         runtime_dispatch_table: &DispatchTable,
-    ) -> Result<Self, failure::Error> {
+    ) -> Result<Self, anyhow::Error> {
         let library = TempLibrary::new(library_path)?;
 
         // Check whether the library has a symbols function
@@ -150,7 +150,7 @@ impl Assembly {
         &mut self,
         library_path: &Path,
         runtime_dispatch_table: &mut DispatchTable,
-    ) -> Result<(), failure::Error> {
+    ) -> Result<(), anyhow::Error> {
         let mut new_assembly =
             Assembly::load(library_path, self.allocator.clone(), runtime_dispatch_table)?;
 
diff --git a/crates/mun_runtime/src/assembly/temp_library.rs b/crates/mun_runtime/src/assembly/temp_library.rs
index 7a89f21c5..af8a24208 100644
--- a/crates/mun_runtime/src/assembly/temp_library.rs
+++ b/crates/mun_runtime/src/assembly/temp_library.rs
@@ -1,7 +1,7 @@
 use std::fs;
 use std::path::Path;
 
-use failure::Error;
+use anyhow::Error;
 use libloading::Library;
 
 /// A structure that holds a `Library` instance but creates a unique file per load. This enables
diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs
index c8b08930b..47cc9aba1 100644
--- a/crates/mun_runtime/src/lib.rs
+++ b/crates/mun_runtime/src/lib.rs
@@ -13,7 +13,7 @@ mod marshal;
 mod reflection;
 mod struct_ref;
 
-use failure::Error;
+use anyhow::Error;
 use garbage_collector::GarbageCollector;
 use memory::gc::{self, GcRuntime};
 use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
diff --git a/crates/mun_runtime_capi/Cargo.toml b/crates/mun_runtime_capi/Cargo.toml
index 7fd8901bd..4b43cb0c9 100644
--- a/crates/mun_runtime_capi/Cargo.toml
+++ b/crates/mun_runtime_capi/Cargo.toml
@@ -17,7 +17,7 @@ name = "mun_runtime"
 crate-type = ["cdylib"]
 
 [dependencies]
-failure = "0.1.7"
+anyhow = "1.0.31"
 lazy_static = "1.4.0"
 abi = { version = "=0.2.0", path = "../mun_abi", package = "mun_abi" }
 memory = { version = "=0.1.0", path = "../mun_memory", package = "mun_memory" }
diff --git a/crates/mun_runtime_capi/src/gc.rs b/crates/mun_runtime_capi/src/gc.rs
index 9c56edcde..c758f263a 100644
--- a/crates/mun_runtime_capi/src/gc.rs
+++ b/crates/mun_runtime_capi/src/gc.rs
@@ -1,7 +1,7 @@
 //! Exposes Mun garbage collection.
 
 use crate::{ErrorHandle, RuntimeHandle, HUB};
-use failure::err_msg;
+use anyhow::anyhow;
 use runtime::Runtime;
 
 pub use memory::gc::GcPtr;
@@ -28,7 +28,7 @@ pub unsafe extern "C" fn mun_gc_alloc(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -37,7 +37,7 @@ pub unsafe extern "C" fn mun_gc_alloc(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'obj' is null pointer."))
+                .register(anyhow!("Invalid argument: 'obj' is null pointer."))
         }
     };
 
@@ -66,7 +66,7 @@ pub unsafe extern "C" fn mun_gc_ptr_type(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -75,7 +75,7 @@ pub unsafe extern "C" fn mun_gc_ptr_type(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'type_info' is null pointer."))
+                .register(anyhow!("Invalid argument: 'type_info' is null pointer."))
         }
     };
 
@@ -103,7 +103,7 @@ pub unsafe extern "C" fn mun_gc_root(handle: RuntimeHandle, obj: GcPtr) -> Error
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -130,7 +130,7 @@ pub unsafe extern "C" fn mun_gc_unroot(handle: RuntimeHandle, obj: GcPtr) -> Err
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -159,7 +159,7 @@ pub unsafe extern "C" fn mun_gc_collect(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -168,7 +168,7 @@ pub unsafe extern "C" fn mun_gc_collect(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'reclaimed' is null pointer."))
+                .register(anyhow!("Invalid argument: 'reclaimed' is null pointer."))
         }
     };
 
diff --git a/crates/mun_runtime_capi/src/hub.rs b/crates/mun_runtime_capi/src/hub.rs
index 683c6830c..2fe6d7c4f 100644
--- a/crates/mun_runtime_capi/src/hub.rs
+++ b/crates/mun_runtime_capi/src/hub.rs
@@ -72,7 +72,7 @@ where
 #[derive(Default)]
 pub struct Hub {
     /// Error registry
-    pub errors: Arc<Registry<ErrorHandle, failure::Error>>,
+    pub errors: Arc<Registry<ErrorHandle, anyhow::Error>>,
 }
 
 lazy_static! {
diff --git a/crates/mun_runtime_capi/src/lib.rs b/crates/mun_runtime_capi/src/lib.rs
index f5ac3da6c..71114f037 100644
--- a/crates/mun_runtime_capi/src/lib.rs
+++ b/crates/mun_runtime_capi/src/lib.rs
@@ -16,7 +16,7 @@ use std::{os::raw::c_char, time::Duration};
 
 use crate::error::ErrorHandle;
 use crate::hub::HUB;
-use failure::err_msg;
+use anyhow::anyhow;
 use runtime::Runtime;
 
 pub(crate) type Token = usize;
@@ -91,19 +91,19 @@ pub unsafe extern "C" fn mun_runtime_create(
     if library_path.is_null() {
         return HUB
             .errors
-            .register(err_msg("Invalid argument: 'library_path' is null pointer."));
+            .register(anyhow!("Invalid argument: 'library_path' is null pointer."));
     }
 
     if options.num_functions > 0 && options.functions.is_null() {
         return HUB
             .errors
-            .register(err_msg("Invalid argument: 'functions' is null pointer."));
+            .register(anyhow!("Invalid argument: 'functions' is null pointer."));
     }
 
     let library_path = match CStr::from_ptr(library_path).to_str() {
         Ok(path) => path,
         Err(_) => {
-            return HUB.errors.register(err_msg(
+            return HUB.errors.register(anyhow!(
                 "Invalid argument: 'library_path' is not UTF-8 encoded.",
             ))
         }
@@ -114,7 +114,7 @@ pub unsafe extern "C" fn mun_runtime_create(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'handle' is null pointer."))
+                .register(anyhow!("Invalid argument: 'handle' is null pointer."))
         }
     };
 
@@ -183,14 +183,14 @@ pub unsafe extern "C" fn mun_runtime_get_function_definition(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
     if fn_name.is_null() {
         return HUB
             .errors
-            .register(err_msg("Invalid argument: 'fn_name' is null pointer."));
+            .register(anyhow!("Invalid argument: 'fn_name' is null pointer."));
     }
 
     let fn_name = match CStr::from_ptr(fn_name).to_str() {
@@ -198,7 +198,7 @@ pub unsafe extern "C" fn mun_runtime_get_function_definition(
         Err(_) => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'fn_name' is not UTF-8 encoded."))
+                .register(anyhow!("Invalid argument: 'fn_name' is not UTF-8 encoded."))
         }
     };
 
@@ -207,7 +207,7 @@ pub unsafe extern "C" fn mun_runtime_get_function_definition(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'has_fn_info' is null pointer."))
+                .register(anyhow!("Invalid argument: 'has_fn_info' is null pointer."))
         }
     };
 
@@ -216,7 +216,7 @@ pub unsafe extern "C" fn mun_runtime_get_function_definition(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'fn_info' is null pointer."))
+                .register(anyhow!("Invalid argument: 'fn_info' is null pointer."))
         }
     };
 
@@ -251,7 +251,7 @@ pub unsafe extern "C" fn mun_runtime_update(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'runtime' is null pointer."))
+                .register(anyhow!("Invalid argument: 'runtime' is null pointer."))
         }
     };
 
@@ -260,7 +260,7 @@ pub unsafe extern "C" fn mun_runtime_update(
         None => {
             return HUB
                 .errors
-                .register(err_msg("Invalid argument: 'updated' is null pointer."))
+                .register(anyhow!("Invalid argument: 'updated' is null pointer."))
         }
     };
 
diff --git a/crates/mun_target/Cargo.toml b/crates/mun_target/Cargo.toml
index dd49722a0..d2acd46c4 100644
--- a/crates/mun_target/Cargo.toml
+++ b/crates/mun_target/Cargo.toml
@@ -14,7 +14,7 @@ categories = ["game-development", "mun"]
 
 [dependencies]
 log = "0.4.8"
-failure = "0.1.7"
+thiserror = "1.0.19"
 
 [dev-dependencies]
 insta="0.16"
diff --git a/crates/mun_target/src/spec.rs b/crates/mun_target/src/spec.rs
index 8d77ee870..640cf2db2 100644
--- a/crates/mun_target/src/spec.rs
+++ b/crates/mun_target/src/spec.rs
@@ -2,7 +2,7 @@ mod apple_base;
 mod linux_base;
 mod windows_msvc_base;
 use crate::host_triple;
-use failure::Fail;
+use thiserror::Error;
 
 #[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash)]
 pub enum LinkerFlavor {
@@ -80,12 +80,12 @@ impl Default for TargetOptions {
         }
     }
 }
-#[derive(Fail, Debug)]
+#[derive(Error, Debug)]
 pub enum LoadTargetError {
-    #[fail(display = "target not found: {}", 0)]
+    #[error("target not found: {0}")]
     BuiltinTargetNotFound(String),
 
-    #[fail(display = "{}", 0)]
+    #[error("{0}")]
     Other(String),
 }
 
diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml
index 30abce6a8..eb885e844 100644
--- a/crates/tools/Cargo.toml
+++ b/crates/tools/Cargo.toml
@@ -5,9 +5,9 @@ authors = ["The Mun Team <team@mun-lang.org>"]
 edition = "2018"
 
 [dependencies]
-teraron = "0.0.1"
+teraron = "0.1.0"
 clap = "2.32.0"
-failure = "0.1.7"
+anyhow = "1.0.31"
 ron = "0.4.2"
 cbindgen = "= 0.14.2"
 bindgen = "0.51"
diff --git a/crates/tools/src/abi.rs b/crates/tools/src/abi.rs
index 0d2244f82..e4ab3654b 100644
--- a/crates/tools/src/abi.rs
+++ b/crates/tools/src/abi.rs
@@ -1,5 +1,5 @@
 use crate::{project_root, reformat, update, Result};
-use failure::format_err;
+use anyhow::anyhow;
 use teraron::Mode;
 
 pub const ABI_DIR: &str = "crates/mun_abi";
@@ -44,7 +44,7 @@ pub fn generate(mode: Mode) -> Result<()> {
 
     let input_file_str = input_file_path
         .to_str()
-        .ok_or_else(|| failure::err_msg("could not create path to mun_abi.h"))?;
+        .ok_or_else(|| anyhow!("could not create path to mun_abi.h"))?;
     let bindings = bindgen::Builder::default()
         .header(input_file_str)
         .whitelist_type("Mun.*")
@@ -60,7 +60,7 @@ pub fn generate(mode: Mode) -> Result<()> {
         .raw_line("#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]")
         .raw_line("use crate::{StructMemoryKind, TypeGroup};")
         .generate()
-        .map_err(|_| format_err!("Unable to generate bindings from 'mun_abi.h'"))?;
+        .map_err(|_| anyhow!("Unable to generate bindings from 'mun_abi.h'"))?;
 
     let file_contents = reformat(bindings.to_string())?;
     update(&output_file_path, &file_contents, mode)
diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs
index 39edcf386..416b1b11a 100644
--- a/crates/tools/src/lib.rs
+++ b/crates/tools/src/lib.rs
@@ -1,5 +1,6 @@
-pub type Result<T> = std::result::Result<T, failure::Error>;
+pub type Result<T> = std::result::Result<T, anyhow::Error>;
 
+use anyhow::bail;
 use std::fs;
 use std::io::Write;
 use std::path::{Path, PathBuf};
@@ -22,7 +23,7 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
 
     if mode == Mode::Verify {
         let changes = difference::Changeset::new(&old_contents, &contents, "\n");
-        failure::bail!("`{}` is not up-to-date:\n{}", path.display(), changes);
+        bail!("`{}` is not up-to-date:\n{}", path.display(), changes,);
     }
     eprintln!("updating {}", path.display());
     fs::write(path, contents)?;
diff --git a/crates/tools/src/syntax.rs b/crates/tools/src/syntax.rs
index 69311540a..7f4a7c705 100644
--- a/crates/tools/src/syntax.rs
+++ b/crates/tools/src/syntax.rs
@@ -1,4 +1,5 @@
 use crate::{project_root, reformat, update, Result};
+use anyhow::anyhow;
 use std::fs;
 use std::path::Path;
 use teraron::Mode;
@@ -26,7 +27,7 @@ fn generate_from_template(template: &Path, src: &Path, mode: Mode) -> Result<()>
         let text = fs::read_to_string(src)?;
         ron::de::from_str(&text)?
     };
-    let content = teraron::render(&template, src)?;
+    let content = teraron::render(&template, src).map_err(|e| anyhow!("{}", e))?;
     let content = reformat(content)?;
     update(&tgt, &content, mode)
 }


Test patch:
diff --git a/crates/mun_runtime/tests/util.rs b/crates/mun_runtime/tests/util.rs
index 5b58fa4ce..5c1e803df 100644
--- a/crates/mun_runtime/tests/util.rs
+++ b/crates/mun_runtime/tests/util.rs
@@ -22,7 +22,7 @@ enum RuntimeOrBuilder {
 }
 
 impl RuntimeOrBuilder {
-    pub fn spawn(&mut self) -> Result<(), failure::Error> {
+    pub fn spawn(&mut self) -> Result<(), anyhow::Error> {
         let previous = std::mem::replace(self, RuntimeOrBuilder::Pending);
         let runtime = match previous {
             RuntimeOrBuilder::Runtime(runtime) => runtime,
@@ -71,7 +71,7 @@ impl TestDriver {
     }
 
     /// Spawns a `Runtime` from the `RuntimeBuilder`, if it hadn't already been spawned.
-    pub fn spawn(&mut self) -> Result<(), failure::Error> {
+    pub fn spawn(&mut self) -> Result<(), anyhow::Error> {
         self.runtime.spawn().map(|_| ())
     }