Surprisingly, anyhow::Error does not implement clone()
or copy()
. What's the proper approach when a copy of an error is needed?
(Use case: I/O error reading game asset can result in multiple objects being tagged with a non-fatal error.)
Surprisingly, anyhow::Error does not implement clone()
or copy()
. What's the proper approach when a copy of an error is needed?
(Use case: I/O error reading game asset can result in multiple objects being tagged with a non-fatal error.)
Rust's io::Error
is not cloneable either. I recommend replacing the error object with your own type.
You can store it as Arc<anyhow::Error>
. But yeah, the practice is that operational errors are not values. They are neither Clone nor Eq, and really work only for bubbling out and displaying. If you need to store an error, it's better to lower it to some POD representation, like
#[derive(Clone, Eq, Hash, Serialize)]
pub struct CannedError {
pub code: i32,
pub message: String
}
impl From<io::Error> for CannedError { ... }
The other posts are giving good answers to your actual problem, but I think this "Surprisingly" is a good learning opportunity. What would it mean if it did implement those?
If it was Copy
, then it couldn't store anything dynamically-allocated. That means no String
s, for the simplest example, which would be quite limiting.
To provide Clone
, it would only be able to store cloneable errors -- since Clone
isn't fallible -- which as kornel mentions would keep it from being useful with common std errors. Maybe one day it will be able to use specialization to offer a try_clone
kind of API, but that doesn't exist yet on stable.
That's the most useful solution.
Why are the standard errors not cloneable? Tradition? Fear of causing allocation during destruction?
Flexibility, backwards compatibility:
let _ = io::Error::new(io::ErrorKind::Other, MyNonCloneError);
I think there’s a much more specific reason. Many errors (including io::Error) store a type-erased variant:
Box<dyn error::Error + Send + Sync>
Rust just doesn’t allow such dyn
objects to be Clone
or Eq
in a nice way. Even if you add Clone
and Eq
to the Error’s super-traits, this won’t just extend to a trait object, you’ll have to do some extra dancing, like here:
That dancing doesn't have to be particularly onerous. A while back I answered a question about a blog post which found a similar way to impl Clone
for Box<dyn MyTrait>
. The trick is to add a supertrait with fn clone_box(&self) -> Box<dyn MyTrait>
, add a blanket impl for all MyTrait + Clone + 'static
types, and call self.clone_box()
in the final Clone
impl (playground):
pub trait DNSRecord: DNSRecordClone {
fn get_name(&self) -> String;
fn get_type(&self) -> u16;
fn get_class(&self) -> u16;
fn get_ttl(&self) -> u32;
fn get_rdlength(&self) -> u16;
fn get_rdata(&self) -> String;
}
pub trait DNSRecordClone {
fn clone_box(&self) -> Box<dyn DNSRecord>;
}
impl<T: DNSRecord + Clone + 'static> DNSRecordClone for T {
fn clone_box(&self) -> Box<dyn DNSRecord> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn DNSRecord> {
fn clone(&self) -> Box<dyn DNSRecord> {
self.clone_box()
}
}
(Someone could probably write a proc macro for this.)
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.