How does one read and understand Rust traces with Opaque errors?
Example Error
For example, consider the following typically long error. How do you infer the problem from the error output?
error[E0599]: the method `next` exists for struct `Pin<&mut impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>>`, but its trait bounds were not satisfied
--> src/main.rs:198:31
|
198 | while let Some(x) = ticks.next().await {
| ^^^^ method cannot be called on `Pin<&mut impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>>` due to unsatisfied trait bounds
|
::: ../toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/pin.rs:408:1
|
408 | pub struct Pin<P> {
| -----------------
| |
| doesn't satisfy `_: StreamExt`
| doesn't satisfy `_: futures::Stream`
|
= note: the following trait bounds were not satisfied:
`Pin<&mut impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>>: futures::Stream`
which is required by `Pin<&mut impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>>: StreamExt`
`impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>: futures::Stream`
which is required by `impl futures::Future<Output = AsyncStream<Result<u32, Box<dyn StdError>>, Opaque(DefId(0:51 ~ armadillo_rust[2000]::query_all::{opaque#0}::{opaque#0}), ['_#6r])>>: StreamExt`
Minimal, Working Example:
use futures::StreamExt;
use async_stream::AsyncStream;
async fn query_all<'a>(date: &'a str) -> AsyncStream<Result<u32, Box<dyn std::error::Error + 'a>>, impl futures::Future + 'a> {
todo!();
}
#[tokio::main]
async fn main() {
let ticks = query_all("2022-06-14");
futures::pin_mut!(ticks);
while let Some(x) = ticks.next().await {
println!("{:?}", x);
}
}
Naive Solution
The error seems to be saying two things:
A pinned stream doesn't satisfy necessary bounds
The return type of query_all fails to implement certain traits.
However, confusingly, the exact same types work in this minimal example:
#[tokio::main]
async fn main() {
let ticks = stream! {
yield Ok(0);
yield Err("");
};
futures::pin_mut!(ticks);
while let Some(x) = ticks.next().await {
println!("{:?}", x);
}
}
How am I misreading this and what is Rust really trying to say?
Related
I'm trying to create a sort-of observer pattern in rust and I am getting a compile error that is basically telling me (I think) it doesn't know the size of a function param at runtime. I didn't realize the size of a function pointer was unknown at runtime. More importantly, I don't have a clue how to tell it the size
christianb#christianb-mac debug % cargo build
Compiling pyrsia-blockchain v0.1.0 (/Users/christianb/dev/jfrog/rusty-brown)
error[E0277]: the size for values of type `(dyn Fn(Foo) -> Result<(), (dyn std::error::Error + 'static)> + 'static)` cannot be known at compilation time
--> src/main.rs:45:16
|
45 | observers: HashMap<&'a Foo, OnFooDone>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn Fn(Foo) -> Result<(), (dyn std::error::Error + 'static)> + 'static)`
note: required by a bound in `HashMap`
error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
--> src/main.rs:45:16
|
45 | observers: HashMap<&'a Foo, OnFooDone>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn std::error::Error + 'static)`
note: required by a bound in `Result`
error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
--> src/main.rs:49:54
|
49 | pub fn submit_foo(&mut self, foo: &Foo, on_done: OnFooDone) -> &Self {
| ^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn std::error::Error + 'static)`
note: required by a bound in `Result`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `pyrsia-blockchain` due to 3 previous errors
use std::collections::HashMap;
use std::error::Error;
fn main() {
let mut holder = Holder {
observers: HashMap::new()
};
let foo0 = Foo {
id: 0,
stuff: **"hello",
};
let foo1 = Foo {
id: 1,
stuff: **"world",
};
let mut foo2 = Foo {
id: 2,
stuff: **"bob",
};
let mut magic_num = 5;
let mut closure = |foo| {
println!("Slow");
magic_num += foo.id;
foo.id
};
holder.submit_foo(&foo0, |f| {
println!("received foo {}", f.id)?
});
holder.submit_foo(&foo1, |f| {
println!("received foo2 {}", f.id)?
});
holder.submit_foo(&foo2, closure);
holder.notify_all();
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Foo {
id: u64,
stuff: str,
}
type OnFooDone = dyn Fn(Foo) -> Result<(),dyn Error>;
pub struct Holder<'a> {
observers: HashMap<&'a Foo, OnFooDone>,
}
impl Holder<'_> {
pub fn submit_foo(&mut self, foo: &Foo, on_done: OnFooDone) -> &Self {
self.observers.insert(foo, on_done);
self
}
pub fn notify_all(self) -> Self {
self.observers.iter().for_each(|k, f| f(k));
self
}
}
The problem is with:
type OnFooDone = dyn Fn(Foo) -> Result<(),dyn Error>;
This is a so-called unsized type because its size is not known at compile time. It's not a function pointer.
Let's start with the type itself. Imagine if you have just a regular function, but also a closure. It's pretty obvious now, that the closure is not the same size as the function pointer, but both implement Fn.
The same issue is present with the Result. What if the function returns several different types that implement dyn Error ?
The solution is to either use references or box the dynamically sized types:
You can return a boxed error, such as Box<dyn Error>> instead of just dyn Error
You can either box the Fn, just like we did with error above, or store a reference: HashMap<&'a Foo, &'a OnFooDone>
Thus we get:
type OnFooDone = dyn Fn(Foo) -> Result<(),Box<dyn Error>>;
pub struct Holder<'a> {
observers: HashMap<&'a Foo, &'a OnFooDone>,
// or with a boxed triat
observers: HashMap<&'a Foo, Box<OnFooDone>>,
}
Resources:
Returning closures
I'm trying to define a struct representing a function that can be composed using different arithmetic operations (only addition has been implemented).
I would like to implement Clone for my struct, however I can't seem to it to work:
use std::ops::Add;
use std::boxed::Box;
use std::clone::Clone;
type InputT = i32;
type OutputT = f64;
pub struct ComposableFn<'a> {
f: Box<dyn 'a + Fn(InputT) -> OutputT>,
}
impl<'a> ComposableFn<'a> {
pub fn new<F: 'a + Fn(InputT) -> OutputT>(f: F) -> Self {
Self {
f: Box::new(f)
}
}
pub fn compute(&self, x: InputT) -> OutputT {
(self.f)(x)
}
}
impl<'a> Add<&'a ComposableFn<'a>> for &'a ComposableFn<'a> {
type Output = ComposableFn<'a>;
fn add(self, rhs: &'a ComposableFn) -> Self::Output {
ComposableFn::new(move |x| self.compute(x) + rhs.compute(x))
}
}
impl<'a> Clone for ComposableFn<'a> {
fn clone(&'a self) -> Self {
ComposableFn::new(move |x| self.compute(x))
}
}
fn main() {
let id = ComposableFn::new(|x| x.into());
println!("{}", id.compute(12));
let double = &id + &id;
println!("{}", double.compute(7));
let triple = &double + &id;
println!("{}", triple.compute(3));
}
When compiling I get the following error:
error[E0308]: method not compatible with trait
--> src/main.rs:33:5
|
33 | fn clone(&'a self) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
|
= note: expected fn pointer `fn(&ComposableFn<'a>) -> ComposableFn<'_>`
found fn pointer `fn(&'a ComposableFn<'a>) -> ComposableFn<'_>`
note: the anonymous lifetime #1 defined on the method body at 33:5...
--> src/main.rs:33:5
|
33 | fn clone(&'a self) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'a` as defined on the impl at 32:6
--> src/main.rs:32:6
|
32 | impl<'a> Clone for ComposableFn<'a> {
| ^^
error: aborting due to previous error
Removing the 'a from fn clone(&'a self) results in the following error instead:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:34:27
|
34 | ComposableFn::new(move |x| self.compute(x))
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 33:5...
--> src/main.rs:33:5
|
33 | fn clone(&self) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that the types are compatible
--> src/main.rs:34:27
|
34 | ComposableFn::new(move |x| self.compute(x))
| ^^^^^^^^^^^^^^^^^^^^^^^^
= note: expected `&ComposableFn<'_>`
found `&ComposableFn<'a>`
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 32:6...
--> src/main.rs:32:6
|
32 | impl<'a> Clone for ComposableFn<'a> {
| ^^
note: ...so that the expression is assignable
--> src/main.rs:34:9
|
34 | ComposableFn::new(move |x| self.compute(x))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: expected `ComposableFn<'a>`
found `ComposableFn<'_>`
error: aborting due to previous error
Is there a way to fix this?
You cannot implement Clone this way. Clone requires the return type to match exactly, which means the exact same lifetime 'a. But you're trying to make a clone that references self which has a different lifetime.
The straightforward solution would be to just clone f. Unfortunately, you can't clone a Box<dyn...>, at least not without some help. See: How to clone a struct storing a boxed trait object?
The only direct solution would be to swap Box out with Rc so they share ownership:
use std::rc::Rc;
pub struct ComposableFn<'a> {
f: Rc<dyn 'a + Fn(InputT) -> OutputT>,
}
impl Clone for ComposableFn<'_> {
fn clone(&self) -> Self {
ComposableFn { f: self.f.clone() }
}
}
Is there a good way to store a heterogeneous list of iterators or objects that can be converted to iterators that can be used multiple times? I've a situation where I'd like to iterate over a vector in multiple ways. Most often, these indices will come from a range or another vector, but that's not a guarantee. In addition, these indices will be used multiple times, so they must be reusable. Is there a good way to accomplish this? As one attempt, we have the code:
// Create a struct to hold a list of items that generate indices and a vector
struct Foo {
data: Vec<f64>,
index_list: Vec<Box<dyn Iterator<Item = usize>>>,
}
// Function that iterates over elements in the data
fn bar(f: &mut Foo) {
for indices in &mut f.index_list {
println!("Start of iteration");
for i in indices {
println!("{}", f.data[i])
}
}
}
// Test the routine
fn main() {
let mut f = Foo {
data: vec![0.1, 1.2, 2.3, 3.4],
index_list: vec![
Box::new((0..=1).into_iter()),
Box::new(vec![2, 1, 3].into_iter()),
],
};
// Want these two functions to not consume the iterators and produce the
// same result
bar(&mut f);
bar(&mut f);
}
This produces
Start of iteration
0.1
1.2
Start of iteration
2.3
1.2
3.4
Start of iteration
Start of iteration
which should be expected. The iterators from the Foo structure are consumed the first time bar is called. I would like to pass f to foo in an immutable fashion, so that multiple calls to foo will return the same result. I also strongly do not want to collect the indices into a vector since the actual use case will involve a very long list of indices that themselves are very large. As such, there are memory limitations that are not necessarily present in this example.
Anyway, removing the mutability above gives a compiler error:
error[E0277]: `&Box<dyn Iterator<Item = usize>>` is not an iterator
--> src/main.rs:14:18
|
14 | for i in indices {
| ^^^^^^^ `&Box<dyn Iterator<Item = usize>>` is not an iterator
|
= help: the trait `Iterator` is not implemented for `&Box<dyn Iterator<Item = usize>>`
= note: `Iterator` is implemented for `&mut std::boxed::Box<dyn std::iter::Iterator<Item = usize>>`, but not for `&std::boxed::Box<dyn std::iter::Iterator<Item = usize>>`
= note: required because of the requirements on the impl of `IntoIterator` for `&Box<dyn Iterator<Item = usize>>`
= note: required by `into_iter`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
Attempting to clone indices gives:
error[E0277]: `&Box<dyn Iterator<Item = usize>>` is not an iterator
--> src/main.rs:14:18
|
14 | for i in indices.clone() {
| ^^^^^^^^^^^^^^^ `&Box<dyn Iterator<Item = usize>>` is not an iterator
|
= help: the trait `Iterator` is not implemented for `&Box<dyn Iterator<Item = usize>>`
= note: `Iterator` is implemented for `&mut std::boxed::Box<dyn std::iter::Iterator<Item = usize>>`, but not for `&std::boxed::Box<dyn std::iter::Iterator<Item = usize>>`
= note: required because of the requirements on the impl of `IntoIterator` for `&Box<dyn Iterator<Item = usize>>`
= note: required by `into_iter`
error: aborting due to previous error
and attempting to use cloned on index_list gives:
error[E0277]: the trait bound `dyn Iterator<Item = usize>: Clone` is not satisfied
--> src/main.rs:12:40
|
12 | for indices in f.index_list.iter().cloned() {
| ^^^^^^ the trait `Clone` is not implemented for `dyn Iterator<Item = usize>`
|
= note: required because of the requirements on the impl of `Clone` for `Box<dyn Iterator<Item = usize>>`
error[E0277]: the trait bound `dyn Iterator<Item = usize>: Clone` is not satisfied
--> src/main.rs:12:20
|
12 | for indices in f.index_list.iter().cloned() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `dyn Iterator<Item = usize>`
|
= note: required because of the requirements on the impl of `Clone` for `Box<dyn Iterator<Item = usize>>`
= note: required because of the requirements on the impl of `Iterator` for `Cloned<std::slice::Iter<'_, Box<dyn Iterator<Item = usize>>>>`
= note: required because of the requirements on the impl of `IntoIterator` for `Cloned<std::slice::Iter<'_, Box<dyn Iterator<Item = usize>>>>`
= note: required by `into_iter`
error: aborting due to 2 previous errors
You can store a list of functions that return those iterators:
struct Foo<'a> {
data: Vec<f64>,
index_list: Vec<&'a dyn Fn() -> Box<dyn Iterator<Item = usize>>>,
}
fn bar(f: &mut Foo) {
for indices in &mut f.index_list {
println!("Start of iteration");
for i in indices() {
println!("{}", f.data[i])
}
}
}
fn main() {
let mut f = Foo {
data: vec![0.1, 1.2, 2.3, 3.4],
index_list: vec![
&|| Box::new((0..=1).into_iter()),
&|| Box::new(vec![2, 1, 3].into_iter()),
],
};
bar(&mut f);
bar(&mut f);
}
I cannot handle async functions when writing an async router for hyper.
This code:
use std::collections::HashMap;
use std::future::Future;
type BoxedResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
type CalcFn = Box<dyn Fn(i32, i32) -> dyn Future<Output = BoxedResult<i32>>>;
async fn add(a: i32, b: i32) -> BoxedResult<i32> {
Ok(a + b)
}
async fn sub(a: i32, b: i32) -> BoxedResult<i32> {
Ok(a - b)
}
fn main() {
let mut map: HashMap<&str, CalcFn> = Default::default();
map.insert("add", Box::new(add));
map.insert("sub", Box::new(sub));
println!("map size: {}", map.len());
}
Generates the following compiler error:
error[E0271]: type mismatch resolving `<fn(i32, i32) -> impl std::future::Future {add} as std::ops::FnOnce<(i32, i32)>>::Output == dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
--> src/main.rs:17:23
|
17 | map.insert("add", Box::new(add));
| ^^^^^^^^^^^^^ expected opaque type, found trait std::future::Future
|
= note: expected type `impl std::future::Future`
found type `dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
= note: required for the cast to the object type `dyn std::ops::Fn(i32, i32) -> dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
error[E0271]: type mismatch resolving `<fn(i32, i32) -> impl std::future::Future {sub} as std::ops::FnOnce<(i32, i32)>>::Output == dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
--> src/main.rs:18:23
|
18 | map.insert("sub", Box::new(sub));
| ^^^^^^^^^^^^^ expected opaque type, found trait std::future::Future
|
= note: expected type `impl std::future::Future`
found type `dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
= note: required for the cast to the object type `dyn std::ops::Fn(i32, i32) -> dyn std::future::Future<Output = std::result::Result<i32, std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>>>`
It seems there is a conflict between impl Future and dyn Future, but I have no idea how to handle it.
This happens because impl Future is a concrete unique type while dyn Future is an abstract type. HashMap expects the abstract type since it can only hold instances of a single type.
If we can box the return type of the async functions, we will able to add these futures into a HashMap.
First we need to change the type of CalcFn:
type CalcFn = Box<dyn Fn(i32, i32) -> Pin<Box<dyn Future<Output = i32>>>>;
Then this can do the trick:
let mut map: HashMap<&str, CalcFn> = Default::default();
map.insert("add", Box::new(|a, b| Box::pin(add(a, b))));
map.insert("sub", Box::new(|a, b| Box::pin(sub(a, b))));
println!("map size: {}", map.len());
//map.get("add").unwrap()(2, 3).await
This complete example
simplified Future's Item type, using an i32 instead of a Result. Please also check the full code for your case.
You can also use types from the futures crate like LocalBoxFuture and BoxFuture created by the FutureExt::boxed and FutureExt::boxed_local methods respectively:
use futures::future::{FutureExt, LocalBoxFuture}; // 0.3.5
use std::collections::HashMap;
type BoxedResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
type CalcFn = Box<dyn Fn(i32, i32) -> LocalBoxFuture<'static, BoxedResult<i32>>>;
async fn add(a: i32, b: i32) -> BoxedResult<i32> {
Ok(a + b)
}
async fn sub(a: i32, b: i32) -> BoxedResult<i32> {
Ok(a - b)
}
async fn example() {
let mut map: HashMap<&str, CalcFn> = Default::default();
map.insert("add", Box::new(|a, b| add(a, b).boxed()));
map.insert("sub", Box::new(|a, b| sub(a, b).boxed()));
println!("map size: {}", map.len());
//map.get("add").unwrap()(2, 3).await
}
Here's my test code:
use std::error::Error;
use std::fmt;
struct Handler {
error: String
}
#[derive(Debug)]
struct SpecificError;
impl fmt::Display for SpecificError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SpecificError")
}
}
impl Error for SpecificError {}
impl<E: Error> From<E> for Handler {
fn from(e: E) -> Self {
Handler { error: format!("{}", e) }
}
}
fn fail1() -> Result<(), SpecificError> {
Err(SpecificError)
}
fn fail2() -> Result<(), Box<Error>> {
Err(Box::new(SpecificError))
}
fn handler() -> Result<(), Handler> {
fail1()?;
fail2()?;
Ok(())
}
The call to fail1() is fine, but the call to fail2() doesn't compile:
error[E0277]: the size for values of type `dyn std::error::Error` cannot be known at compilation time
--> src/main.rs:35:5
|
35 | fail2()?;
| ^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `dyn std::error::Error`
= note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<dyn std::error::Error>`
= note: required because of the requirements on the impl of `std::convert::From<std::boxed::Box<dyn std::error::Error>>` for `Handler`
= note: required by `std::convert::From::from`
I agree with the compiler that dyn Error doesn't have a size known at compile time, but I don't understand why that's relevant, since the type I'm attempting to convert from is a Box<dyn Error>, which does have a size known at compile time.
TL;DR: I'm pretty sure that you cannot in a generic way.
I don't understand why that's relevant, since the type I'm attempting to convert from is a Box<dyn Error>, which does have a size known at compile time.
That's not the place it's complaining about. Look at the error message again (slightly cleaned up):
the trait `Sized` is not implemented for `dyn Error`
required because of the requirements on the impl of `Error` for `Box<dyn Error>`
required because of the requirements on the impl of `From<Box<dyn Error>>` for `Handler`
required by `From::from`
The second line is the important one. Here's a simpler reproduction of your problem:
use std::error::Error;
fn example<E: Error>() {}
fn main() {
example::<Box<dyn Error>>();
}
error[E0277]: the size for values of type `dyn std::error::Error` cannot be known at compilation time
--> src/main.rs:6:5
|
6 | example::<Box<dyn Error>>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `dyn std::error::Error`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<dyn std::error::Error>`
note: required by `example`
--> src/main.rs:3:1
|
3 | fn example<E: Error>() {}
| ^^^^^^^^^^^^^^^^^^^^^^
Error is only implemented for Box<T> when T is Sized and implements Error itself:
impl<T: Error> Error for Box<T> {
// ...
}
Said another way, Box<dyn Error> does not implement Error.
One might think that you can add a second implementation of From for Box<Error>, but this is disallowed:
upstream crates may add new impl of trait `std::error::Error` for type
`std::boxed::Box<(dyn std::error::Error + 'static)>` in future versions
The best alternative I have to offer is to implement From for each individual concrete type you need to support:
impl From<SpecificError> for Handler {
fn from(e: SpecificError) -> Self {
Handler { error: format!("{}", e) }
}
}
impl From<Box<dyn Error>> for Handler {
fn from(e: Box<dyn Error>) -> Self {
Handler { error: format!("{}", e) }
}
}
A macro can reduce the boilerplate here.