Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC to require impl MyStruct to be nearby the definition of MyStruct #155

Merged
merged 1 commit into from
Sep 7, 2014

Conversation

apoelstra
Copy link
Contributor

This fixes #15060 and #3785.

@adrientetar
Copy link

@SiegeLord
Copy link

I have found this feature useful in my library to encompass the state of a foreign library and do dependency injection in a clean way. Here's a snippet of how its used:

mod a
{
    pub struct Bitmap
    {
        priv_field: () // note this field
    }

    impl super::Core
    {
        pub fn create_bitmap(&self) -> Bitmap
        {
            Bitmap{ priv_field: () }
        }
    }
}

pub struct Core;

impl Core
{
    pub fn init_library() -> Core
    {
        Core
    }
}

fn main()
{
    let core = Core::init_library();
    let bmp = core.create_bitmap();
}

I suppose I could work around this by using a macro that looks like this:

mod a
{
    pub struct Bitmap
    {
        priv_field: ()
    }

    pub mod export
    {
        cross_impl!
        {
            impl Core
            {
                pub fn create_bitmap(&self) -> Bitmap
                {
                    Bitmap{ priv_field: () }
                }
            }
        }

        // Expands to:

        /*
        pub trait _AnonTrait_XXXXXX
        {
            fn create_bitmap(&self) -> Bitmap;
        }

        impl _AnonTrait_XXXXXX for Core
        {
            fn create_bitmap(&self) -> Bitmap
            {
                Bitmap{ priv_field: () }
            }
        }
        */
    }
}

pub use a::export::*;
pub struct Core;

But if I can do this via a macro, what prevents the compiler from doing it?

@apoelstra
Copy link
Contributor Author

@SiegeLord if you add any static methods to your impl super::Core they will not be visible anywhere and lead to confusing error messages. (This is what I was referring to when I said "only lucky users are doing this".)

If you use non-anonymous traits as described, you cannot have static methods that are accessible as Core::init_library(). You would need to do something like let core: Core = Trait::init_library(), so the trait name needs to be user-visible, defeating the hiding provided by the macro.

Edit: I think it doable to simply prevent static methods in impl long::path::to::Struct and impl StructThatIsntSeenYet while leaving non-static methods be. resolve doesn't care about non-static methods except to provide pretty error messages to users who call them out of context.

Edit2: If we were to take this approach, the pretty error messages would be missing in the currently-broken cases, which would still be a bug, though a minor one.

````
which @Ryman noticed in https://github.com/rust-lang/rust/issues/15060 . It is
not clear to me why this one fails, since the failure is in typeck, which I am
not familiar with.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized this has nothing to do with typeck, that's just where "module instead of type" is noticed. The reason that Vec in impl<U: T> Vec<U> resolves to a module here rather than a type is in Resolver::def_for_namespace(), which is eventually called from Resolver::resolve_crate(), the final function call of Resolver::resolve(). (The purpose of resolve_crate() is to go through once every struct and impl has been seen to resolve things completely.)

The way that def_for_namespace() works is to try and return a DefType first, which it will only find if the type Vec had been defined somewhere. Failing that, try and return a DefMod, which will be defined if an impl Vec or struct Vec had occured somewhere. Failing that, return None.

Notice that at no point are imports used when resolving Vec. This could happen when resolving Vec::from_slice() later on, in Resolver::resolve_module_path(), which first tries to find Vec in the current module, then failing that, resolves imports. (In fact this would not happen, since Vec would be found in the current module, as a module rather than type, thanks to the impl Vec and the anonymous trait module it creates.) But this doesn't even have a chance to happen, since, resolve_module_path() is never called when resolving impl Vec; impl blocks and functions become different beasts during the initial build_reduced_graph() run, so different code paths are taken in resolve_crate(). (And this makes sense, since when resolving impl Vec in resolve_crate(), we know that Vec will is in the module because we added it in build_reduced_graph() as an anonymous trait module. So there is no point in resolving imports.)

@apoelstra
Copy link
Contributor Author

@SiegeLord I notice that in src/libstd/io/fs.rs there is impl path::Path which does exactly what you describe. It really is cleaner this way. Dang. So I will rethink my rules and try to come up with a consistent RFC which is (a) consistent, (b) lets you do this, and (c) doesn't expose the limitations of resolve through bizarre bugs.

Edit: In fact, I don't think there's a way (short of seriously refactoring resolve) to handle static methods for anonymous traits on elsewhere-defined structs. And I don't want to say "only non-static methods on impls, unless the struct is defined in the same file" because that's an extremely specific rule to memorize and clearly originates in compiler limitations. Let's not be like certain other languages which have a bajillion such rules :)

My feeling for simplicity's sake is to go with the original RFC, sans the "impl must occur after struct" restriction, and say that anonymous impls have to occur only in the defining module.

@sfackler
Copy link
Member

sfackler commented Jul 7, 2014

👍 Even if resolve handled it well, I think it'd be best to restrict anonymous impls to the defining module.

@dobkeratops
Copy link

I'd be interested in a move in the opposite direction .. fewer restrictions on what can be impl'd where, and the option of duck type interfaces to cut down on the noise when dealing with single function traits

…tion of `MyStruct`

This fixes #15060 and #3785.
@apoelstra
Copy link
Contributor Author

Pushed changes which fix my own line comments (remove requirement to have impl after struct and minor improvements to description of resolve process).

@apoelstra
Copy link
Contributor Author

@pcwalton
Copy link
Contributor

pcwalton commented Jul 9, 2014

+1, language simplification.

@apoelstra
Copy link
Contributor Author

I'm happy to implement this if it gains support. There are two new error messages to be added:

  • impl aa::bb::cc is not allowed since paths of length > 1 are illegal.
  • impl YetUnseen is not allowed if YetUnseen is not defined as a type in the current module.

We can detect if YetUnseen is simply undefined (though perhaps it appears in a use statement, which we won't see) or defined as something else altogether. We might want to give different error messages in each case.

I'm no good at bikeshedding, so can I get some suggestions for these?

@nikomatsakis
Copy link
Contributor

I am of mixed minds.

On the one hand, this is the conservative choice, works around the various resolve bugs, and can easily be extended later. It also seems to satisfy a sort of encapsulation concern, in that it kind of makes sense for "inherent methods" to be defined close to the type in question.

On the other hand, I think it is frequently useful to be able to split apart the methods on a type. @SiegeLord brings up an interesting usage I hadn't considered. I frequently find I want to define a core type and then define methods related to (e.g.) type checking in the type checker and other things somewhere else. On the other other hand, both of these use cases can readily be accommodated with traits.

One specific concern I have is that violates the rule of thumb that if an action is legal in module M, it is legal in submodules of M. I think this is a good rule, since it ensures that you can always split up a module M into multiple submodules when it gets too complicated. (Still, as before, you can work-around it with traits.)

(I am also a bit disappointed in the idea of choosing our RFCs based on resolve bugs, rather than resolving on what we think the rules should be and filing bugs where we deviate.)

@pcwalton
Copy link
Contributor

@nikomatsakis To me it's just a question of resourcing and priorities. I agree that what you describe is the best behavior, I just want to make sure we have something shippable for 1.0.

@apoelstra
Copy link
Contributor Author

An example of the modularity that @nikomatsakis says is useful appears in @jeremyletang's rgtk. Combining all the impl cairo::Contexts into a single module is not hard to do, but changing filesystem layout for rustc's sake is not really desirable. It also forces you to either expose some private interfaces or combine functionally-distinct modules into one, just like in @SiegeLord's example.

@huonw
Copy link
Member

huonw commented Aug 10, 2014

Another (relatively minor) issue: rust-lang/rust#16398

@rainbow-alex
Copy link

I use impl super::MyStruct and would much rather see it fixed (so I can use MyStruct; impl MyStruct) than removed.

@pcwalton pcwalton merged commit d38d12d into rust-lang:master Sep 7, 2014
pnkfelix added a commit to pnkfelix/rfcs that referenced this pull request Oct 8, 2014
In particular:

* The RFC associated with rust-lang#127 should have had a link to rust-lang#19 as well
  (and has been assigned RFC rust-lang#19); it also was revised to match the
  markdown href style of other RFCs.

* RFC rust-lang#34 needed its header entries filled in,

* RFC rust-lang#123 had a typo in its header, and

* RC rust-lang#155 was revised to match the markdown href style of other RFCs.
@Centril Centril added A-typesystem Type system related proposals & ideas A-inherent-impl Proposals related to inherent implementations labels Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-inherent-impl Proposals related to inherent implementations A-typesystem Type system related proposals & ideas
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants