Need explanation how `context` and `with_context` operate on non-anyhow types
proski opened this issue · comments
Something unusual is happening in the example on the thiserror
and anyhow
page that needs explanation.
https://docs.rs/anyhow/latest/anyhow/trait.Context.html
fs::File::open(path)
.with_context(|| format!("Failed to open {path}"))?
fs::File::open(path)
returns a value of type core::result::Result<std::fs::File, std::io::error::Error>
. Then .with_context
is called on that value and produces a value of type core::result::Result<std::fs::File, anyhow::Error>
with_context
takes self
as the first argument i.e. it should be the original result:
https://docs.rs/anyhow/latest/anyhow/trait.Context.html
But that result is not aware of anyhow
! However, there is something interesting at the bottom of that page. It mentions "Implementations on Foreign Types" which appears to be what we have here. I've been using Rust for months, I've read several books about Rust, and yet it appears completely new to me.
Moreover, it appears for go against the orphan rule mentioned here: https://doc.rust-lang.org/book/ch10-02-traits.html
I even copied and modified the example to check that the return value plays no role:
use anyhow::Context;
use std::fs;
fn print_type_of<T>(tag: &str, _: &T) {
println!("{tag}: {}", core::any::type_name::<T>());
}
fn main() {
let result = fs::File::open("config.dat");
print_type_of("result", &result);
let result_ctx = result.context("Failed to open");
print_type_of("result_ctx", &result_ctx);
}
It prints:
result: core::result::Result<std::fs::File, std::io::error::Error>
result_ctx: core::result::Result<std::fs::File, anyhow::Error>
result_ctx
is not used except to print its type, so there should be no type inference.
I would really appreciate an additional new speaker note on that page mentioning "Implementations on Foreign Types".
Thanks for bringing this up! We don't mention the orphan rule at all, in fact.
This is not a violation of the orphan rule, since the anyhow
crate both defines the Context
trait and implements it for Option
and Result
.
But how does rust find the context
method? for result
in your example? What if some other crate also implemented a trait with a context
method that applied to Result
? The use anyhow::Context
is what signals that this module wishes to use anyhow::Context::context
here. That's a bit awkward since it requires use
ing the trait name, while only the method name appears in the source. I've seen a few people disambiguate this by putting the use
statement closer to the method call:
fn main() {
use anyhow::Context;
let result = fs::File::open("config.dat");
print_type_of("result", &result);
let result_ctx = result.context("Failed to open");
print_type_of("result_ctx", &result_ctx);
}
but I don't think that is a common approach.
Do you want to make a PR to add some content regarding the orphan rule and this "use trait" syntax?
OK, I'll make a PR. Thank you for the explanation!