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 compileWhen 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 RustKey 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.
