How does anyhow::Error::new differ from From implementation for wrapping external error types?

anyhow::Error::new creates an error from any type implementing std::error::Error, wrapping it as a trait object that preserves the full error chain, while From implementations enable ergonomic conversion through the ? operator but require explicit implementations for each source type. The key distinction is that Error::new is explicit and works with any error type at the call site, whereas From<T> for Error provides implicit conversion when the trait is in scope.

Basic anyhow::Error::new Usage

use anyhow::{Error, Result};
use std::io;
 
fn basic_error_new() -> Result<()> {
    let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
    
    // Explicit error creation with Error::new
    let error = Error::new(io_error);
    
    // Add context
    let error = Error::new(io_error).context("failed to read config");
    
    Err(error)
}
 
fn using_error_new() {
    match basic_error_new() {
        Err(e) => println!("{}", e),
        _ => {}
    }
    // Output: failed to read config
    // caused by: file not found
}

Error::new explicitly wraps any error type implementing std::error::Error.

Basic From Implementation Usage

use anyhow::Result;
use std::io;
 
fn using_from_trait() -> Result<()> {
    let file = std::fs::File::open("nonexistent.txt")?;
    // The ? operator uses From::from to convert io::Error to anyhow::Error
    
    Ok(())
}
 
fn from_conversion() -> Result<()> {
    // Explicit From::from conversion
    let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
    let anyhow_error: anyhow::Error = io_error.into();
    
    Err(anyhow_error)
}

The ? operator implicitly uses From to convert errors.

Error::new is Explicit Conversion

use anyhow::{Error, Result};
use std::io;
 
fn explicit_vs_implicit() -> Result<()> {
    let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
    
    // Error::new: explicit conversion at call site
    let error1 = Error::new(io_error);
    
    // From::from: explicit trait call
    let error2 = anyhow::Error::from(io_error);
    
    // .into(): uses From trait
    let io_error = io::Error::new(io::ErrorKind::NotFound, "test");
    let error3: anyhow::Error = io_error.into();
    
    // ?: implicit From usage
    // let error4 = io_error?;  // Would use From::from
    
    Ok(())
}

Error::new is explicit; From enables implicit conversions.

From Trait Implementation in anyhow

use anyhow::Error;
 
// anyhow implements From for common error types
// This allows automatic conversion with ?
 
// impl From<std::io::Error> for Error { ... }
// impl From<serde_json::Error> for Error { ... }
// impl From<reqwest::Error> for Error { ... }
 
fn automatic_conversion() -> anyhow::Result<()> {
    // These all use From implicitly via ?
    
    let _file = std::fs::File::open("config.txt")?;
    // io::Error -> anyhow::Error via From
    
    let _data = serde_json::from_str::<Value>("{}")?;
    // serde_json::Error -> anyhow::Error via From
    
    Ok(())
}

anyhow implements From for common error types to enable ?.

When Error::new is Necessary

use anyhow::{Error, Result};
 
// Custom error type without From implementation
struct CustomError {
    code: i32,
    message: String,
}
 
impl std::fmt::Display for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[{}] {}", self.code, self.message)
    }
}
 
impl std::error::Error for CustomError {}
 
fn custom_error_example() -> Result<()> {
    let custom = CustomError {
        code: 404,
        message: "Not found".to_string(),
    };
    
    // No From<CustomError> for anyhow::Error exists
    // Must use Error::new explicitly
    let error = Error::new(custom);
    
    Err(error)
}
 
fn cannot_use_from() -> Result<()> {
    let custom = CustomError {
        code: 500,
        message: "Internal error".to_string(),
    };
    
    // This would NOT compile:
    // Err(custom)?  // No From implementation
    
    // Must use:
    Err(Error::new(custom))
}

Error::new works with any error type without requiring From implementation.

Error::new Preserves Error Source

use anyhow::{Error, Result};
use std::io;
 
fn error_chain() -> Result<()> {
    let inner = io::Error::new(io::ErrorKind::PermissionDenied, "permission denied");
    
    // Error::new wraps the error, preserving its source
    let error = Error::new(inner);
    
    // Can access source
    if let Some(source) = error.source() {
        println!("Source: {}", source);
    }
    
    Err(error)
}

Error::new preserves the error chain for downstream inspection.

From Trait Enables ? Operator

use anyhow::Result;
use std::io;
 
fn with_question_mark() -> Result<()> {
    // The ? operator requires From<Source> for anyhow::Error
    
    // This works because From<io::Error> for anyhow::Error exists
    let _file = std::fs::File::open("config.txt")?;
    
    // Equivalent to:
    // let _file = match std::fs::File::open("config.txt") {
    //     Ok(f) => f,
    //     Err(e) => return Err(anyhow::Error::from(e)),
    // };
    
    Ok(())
}

The From implementation is what makes ? work seamlessly.

Adding Context to Errors

use anyhow::{Context, Result};
use std::io;
 
fn with_context() -> Result<()> {
    // Using ? with context
    let _file = std::fs::File::open("config.txt")
        .context("failed to open config file")?;
    
    // context() uses From internally to wrap errors
    Ok(())
}
 
fn error_new_with_context() -> Result<()> {
    let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
    
    // Error::new then add context
    let error = Error::new(io_error).context("failed to open config file");
    
    // Or use .context() directly on the Result
    let _file = std::fs::File::open("config.txt")
        .map_err(|e| Error::new(e).context("failed to open config"))?;
    
    Ok(())
}

Both approaches support adding context to errors.

Error::new vs From for Error Wrapping

use anyhow::{Error, Result};
 
trait Downcast {
    fn downcast<T: std::error::Error + 'static>(self) -> Result<T, Error>;
}
 
fn error_wrapping_comparison() -> Result<()> {
    // From approach: automatic, implicit
    fn use_from() -> Result<()> {
        let _: std::fs::File = std::fs::File::open("test.txt")?;
        Ok(())
    }
    
    // Error::new approach: explicit, always works
    fn use_error_new() -> Result<()> {
        let result = std::fs::File::open("test.txt");
        match result {
            Ok(file) => Ok(()),
            Err(e) => Err(Error::new(e)),
        }
    }
    
    // Both produce the same result
    // From is more ergonomic when available
    // Error::new is explicit and universal
    
    Ok(())
}

From is ergonomic; Error::new is universal and explicit.

Custom From Implementations

use anyhow::{Error, Result};
 
// You can implement From for your own error types
#[derive(Debug)]
struct DatabaseError {
    query: String,
    cause: String,
}
 
impl std::fmt::Display for DatabaseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Database error: {} (query: {})", self.cause, self.query)
    }
}
 
impl std::error::Error for DatabaseError {}
 
// Implement From to enable ? conversion
impl From<DatabaseError> for Error {
    fn from(err: DatabaseError) -> Self {
        Error::new(err)
    }
}
 
fn with_custom_from() -> Result<()> {
    let db_error = DatabaseError {
        query: "SELECT * FROM users".to_string(),
        cause: "connection timeout".to_string(),
    };
    
    // Now this works with ? because we implemented From
    Err(db_error)?;
    
    Ok(())
}

Implementing From for your types enables ? conversion.

Error::new Works Without From

use anyhow::{Error, Result};
 
// Error type without From implementation
struct LegacyError {
    code: u32,
}
 
impl std::fmt::Display for LegacyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Legacy error code: {}", self.code)
    }
}
 
impl std::error::Error for LegacyError {}
 
fn legacy_error_handling() -> Result<()> {
    let legacy = LegacyError { code: 42 };
    
    // No From<LegacyError> for anyhow::Error
    // But Error::new still works!
    let error = Error::new(legacy);
    
    Err(error)
}
 
fn cannot_use_question_mark() -> Result<()> {
    let legacy = LegacyError { code: 42 };
    
    // This would NOT compile:
    // Err(legacy)?;
    // error: the trait `From<LegacyError>` is not implemented for `anyhow::Error`
    
    // Must use Error::new:
    Err(Error::new(legacy))
}

Error::new works for any error type without From implementation.

Downcasting with Error::new

use anyhow::{Error, Result};
use std::io;
 
fn downcasting() -> Result<()> {
    let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
    let error = Error::new(io_error);
    
    // Downcast to original type
    if let Some(io_err) = error.downcast_ref::<io::Error>() {
        println!("IO error kind: {:?}", io_err.kind());
    }
    
    // Try downcast consumes the error
    let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
    let error = Error::new(io_error);
    let recovered: io::Error = error.downcast().unwrap();
    
    Ok(())
}

Both Error::new and From preserve the error for downcasting.

Error Construction Patterns

use anyhow::{anyhow, bail, Error, Result};
use std::io;
 
fn error_patterns() -> Result<()> {
    // Pattern 1: Error::new (explicit)
    let io_error = io::Error::new(io::ErrorKind::NotFound, "not found");
    let error1 = Error::new(io_error);
    
    // Pattern 2: From trait (implicit via ?)
    let _file = std::fs::File::open("test.txt")?;
    
    // Pattern 3: anyhow! macro (for string messages)
    let error3 = anyhow!("something went wrong");
    
    // Pattern 4: bail! macro (early return)
    bail!("something went wrong");
    
    // Pattern 5: .context() (wraps with context)
    let _file = std::fs::File::open("test.txt")
        .context("failed to open config")?;
    
    // Pattern 6: with_context() (lazy context)
    let _file = std::fs::File::open("test.txt")
        .with_context(|| format!("failed to open {}", "config"))?;
    
    Ok(())
}

Multiple patterns exist for error construction; Error::new is the most explicit.

When to Use Each Approach

use anyhow::{Error, Result};
 
fn when_to_use() -> Result<()> {
    // Use Error::new when:
    // 1. Working with a type that has no From implementation
    
    struct UniqueError;
    impl std::fmt::Display for UniqueError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "unique error")
        }
    }
    impl std::error::Error for UniqueError {}
    
    let unique = UniqueError;
    let error = Error::new(unique);  // Must use Error::new
    
    // 2. You want explicit error wrapping
    let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
    let error = Error::new(io_err);  // Explicit
    
    // Use From (via ?) when:
    // 1. The type has From implemented
    let _file = std::fs::File::open("test")?;  // Implicit From
    
    // 2. You want ergonomic error propagation
    // ? is cleaner than match/Err(Error::new(...))
    
    Ok(())
}

Choose based on ergonomics and explicitness requirements.

Error Types with From Already Implemented

use anyhow::Result;
 
fn types_with_from() -> Result<()> {
    // These types already have From<T> for anyhow::Error:
    
    // std::io::Error
    let _file = std::fs::File::open("test.txt")?;
    
    // std::string::FromUtf8Error
    let bytes = vec
![0, 159, 146, 150]
;
    // let _s = String::from_utf8(bytes)?;  // Would use From
    
    // std::str::Utf8Error
    // let _s = std::str::from_utf8(&[0, 128])?;  // Would use From
    
    // Many library error types
    // serde_json::Error, reqwest::Error, etc.
    
    // All of these can use ? directly because anyhow implements From
    
    Ok(())
}

Common error types already have From implementations for anyhow::Error.

Error Source Chain Preservation

use anyhow::{Error, Result};
use std::error::Error as StdError;
 
fn source_chain() -> Result<()> {
    let inner1 = std::io::Error::new(
        std::io::ErrorKind::NotFound,
        "file not found"
    );
    let inner2 = Error::new(inner1);
    let outer = inner2.context("failed to load config");
    
    // Traverse the error chain
    let mut current: &dyn StdError = outer.as_ref();
    println!("{}", current);
    
    while let Some(source) = current.source() {
        println!("caused by: {}", source);
        current = source;
    }
    
    // Output:
    // failed to load config
    // caused by: file not found
    
    Ok(())
}

Both Error::new and From preserve the error source chain.

Performance Considerations

use anyhow::{Error, Result};
 
fn performance() {
    // Error::new:
    // - Allocates once for the error trait object
    // - Wraps the source error
    // - Direct, explicit call
    
    // From::from:
    // - Also allocates for the trait object
    // - Identical underlying implementation
    // - Called implicitly by ? operator
    
    // Performance is essentially identical
    // Error::new might be marginally faster in debug builds
    // (avoiding trait lookup), but this is negligible
    
    // Both store errors as Box<dyn Error + Send + Sync + 'static>
}

Performance is equivalent; Error::new and From use the same implementation.

Summary Table

use anyhow::Error;
 
fn summary() {
    // | Aspect              | Error::new              | From trait          |
    // |---------------------|-------------------------|---------------------|
    // | Usage               | Error::new(err)         | err? or .into()     |
    // | Explicitness        | Explicit                | Implicit            |
    // | Requirements        | impl Error              | impl From<T>        |
    // | Works with ?        | No (need From)          | Yes                 |
    // | Universal            | Yes (any Error type)   | No (requires impl)  |
    // | Downcast            | Yes                     | Yes                 |
    // | Context support     | Yes                     | Yes                 |
    // | Performance         | Same                    | Same                |
}

Both approaches produce equivalent errors with different ergonomics.

Synthesis

Quick reference:

use anyhow::{Error, Result};
 
// Error::new: explicit, works with any error type
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
let error = Error::new(io_err);
 
// From trait: implicit, requires implementation
fn with_from() -> Result<()> {
    let _file = std::fs::File::open("test.txt")?;
    Ok(())
}
 
// Error::new is necessary when From isn't implemented
struct CustomError;
impl std::fmt::Display for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "custom error")
    }
}
impl std::error::Error for CustomError {}
 
let custom = CustomError;
let error = Error::new(custom);  // Works!
// Err(custom)?  // Would fail to compile

When to use each:

use anyhow::{Error, Result};
 
// Use Error::new when:
// - Working with custom error types without From implementation
// - You want explicit error wrapping
// - Converting errors at specific call sites
// - Working with legacy error types
 
// Use From (via ?) when:
// - The type already has From implemented
// - You want ergonomic error propagation
// - Using standard library or popular crate errors
// - Writing clean, idiomatic Rust

Key insight: anyhow::Error::new and From implementations are two sides of the same coinโ€”Error::new is the explicit constructor that works with any type implementing std::error::Error, while From<T> for Error enables the ergonomic ? operator for types with the trait implementation. The fundamental difference is explicitness vs. ergonomics: Error::new(custom_error) is a direct function call that always works regardless of trait implementations, whereas result? implicitly calls From::from(error) which requires the trait to be implemented. Under the hood, both end up creating a Box<dyn Error + Send + Sync + 'static> that stores the error as a trait object. The From implementations in anyhow for common types like std::io::Error, serde_json::Error, etc., exist precisely so you can use ? without explicit conversion. For custom types, you can either use Error::new directly each time, or implement From once to enable ? everywhere. The Error::new approach is more explicit about where errors come from, while the From approach provides cleaner code when the implementation exists.