reference icon indicating copy to clipboard operation
reference copied to clipboard

Rust reference is missing a description of the visibility rules used for method lookup

Open zygoloid opened this issue 6 years ago • 5 comments

In https://doc.rust-lang.org/reference/expressions/method-call-expr.html, we find:

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  • T's inherent methods (methods implemented directly on T).
  • Any of the methods provided by a visible trait implemented by T. If T is a type parameter, methods provided by trait bounds on T are looked up first. Then all remaining methods in scope are looked up.

Both mentions of "visible" hyperlink to https://doc.rust-lang.org/reference/visibility-and-privacy.html, which does not define what "visible" actually means.

Presumably the set of visible traits in some location has some dependence on preceding use declarations, but this doesn't appear to be documented. And according to https://github.com/rust-lang/rustc-guide/blob/master/src/method-lookup.md, the two uses of "visible" above actually mean two different things -- it looks like the first use of "visible" above means "can be accessed" as defined in visibility-and-privacy, whereas the second also depends on whether the trait has been imported (although it's unclear what precisely "imported" means in this context).

zygoloid avatar Jul 02 '19 23:07 zygoloid

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

The first "visibility" is indeed privacy/accessibility as in "pub", and the link is correct. For inherent methods that's the pub on the method itself, for trait methods that's the pub on its respective trait. Methods not accessible from the call point are filtered away during lookup (so changing visibilities may change lookup results, unlike in C++).

Any of the methods provided by a visible trait implemented by T.

This should be "any of the methods provided by a trait in scope implemented by T", and the link is incorrect.

petrochenkov avatar Jul 03 '19 09:07 petrochenkov

The first "visibility" is indeed privacy/accessibility as in "pub", and the link is correct.

It may be a correct link, but it points to documentation that does not actually document what "visible" means, at least in the most common case. Does plain pub make an item visible? If so, where does it make it visible? This is not documented.

This should be "any of the methods provided by a trait in scope implemented by T", and the link is incorrect.

What does "in scope" mean for a trait?

zygoloid avatar Nov 18 '19 18:11 zygoloid

It may be a correct link, but it points to documentation that does not actually document what "visible" means

Quite possible, the reference is incomplete and occasionally outdated (see the badge at the top). This particular page mixes some ancient informal text in the start with documentation added several years later as a part of implementation for newer features. Yeah, this should be rewritten in a more formal way and with more consistent terminology, but well, human resources.

Does plain pub make an item visible?

TLDR: Yes.

pub X makes X accessible in the sense that if some code can name X (directly or through reexports), then it can use it without an "X is private" error. pub X also makes X visible in the sense that it's not filtered away by glob imports or method lookup, so code trying to use it through glob imports or method lookup can see X at all.

Plain pub makes X accessible/visible to any code, including other crates. pub(in x::y::z) (including sugared forms like pub(crate)) makes X accessible/visible inside the module x::y::z.

Note that pub doesn't guarantee that the item can be named, so it's more like an upper bound on accessibility/visibility.

mod m {
    mod n {
        pub struct S {} // Can't name this due to private outer modules, so it's unusable outside of `m` despite the `pub`
    }
}

petrochenkov avatar Nov 19 '19 19:11 petrochenkov

What does "in scope" mean for a trait?

The trait Tr is in scope at point x is you can refer to it by single-identifier (unqualified) name. (There are a couple of nuances with anonymously imported traits and shadowed traits, but they don't break the general idea.)


// Not in scope

mod m {
    // In scope, `Tr` refers to the trait.

    pub trait Tr {}

    // In scope, `Tr` refers to the trait.

    fn f() {
        // In scope, `Tr` refers to the trait.
        {
                // In scope, `Tr` refers to the trait.
        }
    }

    mod n {
        // In scope, `Br` refers to the trait.

        use super::Tr as Br;

        // In scope, `Br` refers to the trait.
    }

    mod p { // mod items act as barriers for names
        // Not in scope
    }
}

petrochenkov avatar Nov 19 '19 19:11 petrochenkov

There seems to be an additional undocumented rule, where a trait is considered for method resolution inside an impl of that trait even if it has no use/unqualified name. This is visible in the following set of examples with std::fmt traits and a custom trait; they have methods named fmt() and foo() respectively, and the resolution is depending on the enclosing impl block.

use std::fmt;
struct Which;

impl fmt::Display for Which {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // This calls `str as Display` even though `Display` is not in scope
        ("hello world").fmt(f)
    }
}
impl fmt::Debug for Which {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // This calls `str as Debug` even though `Debug` is not in scope
        ("hello world").fmt(f)
    }
}

mod traits {
    pub trait Foo1 {
        fn foo(&self) -> &str;
    }
    pub trait Foo2 {
        fn foo(&self) -> &str;
    }
    impl Foo1 for () {
        fn foo(&self) -> &str { "1" }
    }
    impl Foo2 for () {
        fn foo(&self) -> &str { "2" }
    }
}
impl traits::Foo1 for Which {
    fn foo(&self) -> &str {
        // This calls `() as Foo1` even though `Foo1` is not in scope
        ().foo()
    }
}
impl traits::Foo2 for Which {
    fn foo(&self) -> &str {
        // This calls `() as Foo2` even though `Foo2` is not in scope
        ().foo()
    }
}

fn main() {
    println!("{Which:?} {Which}");
    println!("{} {}", traits::Foo1::foo(&Which), traits::Foo2::foo(&Which));
}

Output:

"hello world" hello world
1 2

Adding use std::fmt::{Display, Debug}; would cause error[E0034]: multiple applicable items in scope.

kpreid avatar Aug 28 '22 22:08 kpreid