Skip to content

Trait fn impl takes precedence over concrete type's fn impl depending on visibility #111443

Open
@rljacobson

Description

@rljacobson

How function names are resolved to concrete implementations on concrete struct and trait objects appears to be incorrect for a specific (but reasonable) corner case.

Suppose we have

  • a concrete struct type MyStruct
  • a method foo_function(…) implemented on MyStruct (i.e. within impl MyStruct{})
  • a trait MyTrait having a trait method foo_function(…)
  • a submodule my_submodule within whatever module contains MyStruct.
  • an implementation of MyTrait for MyStruct, which necessarily has an implementation for the MyTrait foo_function(…) function. (It doesn't matter if it's the default impl of foo_function(…) for MyTrait.)

Which function the expression MyStruct::foo_function(…) resolves to depends on whether the implementation of foo_function(…) for MyType is inside of sub_module. See the code below.

I tried this code:

struct MyStruct{}

impl MyStruct {
    pub fn bar_function(&self) {
        // Which function is called in the following depends on the visibility of the concrete type's `foo_function`.
        // This behavior is NOT exhibited if we move the concrete type's `foo_function` impl to the same module.

        // Call the function on the concrete `MyStruct` instance. Which function is _actually_ called?
        self.foo_function();

        // Same as above but with explicit notation to emphasize we are naming the MyStruct version in the call.
        MyStruct::foo_function(self);
    }

    // The following is an error if its counterpart in `my_submodule` exists.
    // fn foo_function(&self){
    //     println!("a third possibility.");
    // }
}

// Some of the `MyStruct` impl in a submodule, for example, another file to keep the codebase navigable.
mod my_submodule {
    // pub(crate) // Has no effect on behavior in question.
    use crate::{MyStruct};

    impl MyStruct {
        pub // Different behavior depending on whether `fn` is `pub` or not.
        fn foo_function(&self) {
            println!("The MyStruct fn");
        }

        pub fn baz_function(&self) {
            // Resolves to the method immediately above this one in every case.
            MyStruct::foo_function(self);
        }
    }
}

trait MyTrait {
    fn foo_function(&self);
}

impl MyTrait for MyStruct {
    fn foo_function(&self) {
        println!("The trait impl fn.");
    }
}

fn main() {
    let my_concrete_struct: &MyStruct = &MyStruct{};

    my_concrete_struct.foo_function();
    my_concrete_struct.bar_function();
    my_concrete_struct.baz_function();
}

I expected to see this happen:

In any context in which private methods of MyStruct are normally visible, I expect MyStruct::foo_function to resolve to the implementation of foo_function(…) on MyStruct regardless of the visibility of that implementation. In other words, I expect the statement MyStruct::foo_function(my_concrete_struct); and my_concrete_struct.foo_function(); to print The MyStruct fn whenever they are in the implementation of a method on MyStruct. Moreover, I expect a pub annotation on an implementation on MyStruct to modify the visibility of the function outside of the implementation of MyStruct while having no effect on its visiblity within a module.

Instead, this happened:

When the implementation of foo_function on MyStruct is pub (i.e. pub fn foo_function(…)…), the expression MyStruct::foo_function does preferentially resolve to the implementation on MyStruct regardless of where it is called from. However…

When the implementation of foo_function on MyStruct is not pub, that function is inaccessible even to other methods of MyStruct the implementations of which are outside of my_submodule, but remain accessible to methods on MyStruct defined within my_submodule. There appears to be no "fully qualified name" which allows methods outside of my_submodule to access the method inside my_submodule nor a "fully qualified name" which allows methods inside my_submodule to access methods outside of my_submodule except by importing the trait and using MyTrait::foo_function(&self).

Apparently, declaring the foo_function within my_submodule as pub makes it public not just within the struct but within the submodule as well. Likewise, declaring the foo_function within my_submodule without the pub annotation makes it not only private to code outside of the implementation of MyStruct but also private within my_submodule. For other methods on MyStruct defined within my_submodule, the foo_function within my_submodule shadows any definition outside of my_submodule.

Curiously, if there is an implementation of foo_function on MyStruct (as opposed to a trait implementation) outside of my_submodule, that implementation cannot be shadowed by another implementation of foo_function on MyStruct defined within my_submodule . One might expect otherwise considering the notation MyStruct::foo_function is allowed to vary depending on scope.

A consequence of this behavior is that a trait fn impl can take precedence over a concrete type's fn impl when the concrete type's impl is not pub and in a submodule.

Meta

Doesn't appear to depend on compiler version.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-trait-systemArea: Trait systemA-visibilityArea: Visibility / privacyC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions