google / comprehensive-rust

This is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.

Home Page:https://google.github.io/comprehensive-rust/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 useing 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!