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

Path mental model #2155

Closed
wants to merge 2 commits into from
Closed

Conversation

vitiral
Copy link

@vitiral vitiral commented Sep 18, 2017

This RFC proposes to unify the mental model around paths. It does this by:

  • Removing the foo.rs + foo/ "file-dir" module system from RFC 2126 on the grounds (primarily) that the design contains a critical flaw around self::+super:: and did not have enough discussion within the mega-rfc, so it warrants its own RFC
  • Add a feature and a lint to require mod foo to be changed to mod self::foo, so that explicit paths are used everywhere after RFC 2126 is stabilized.
  • Have cargo new create crate/ instead of src/ so that use crate::foo::bar has an accurate mental map of crate/foo/bar.rs instead of src/foo/bar.rs

Rendered

@est31
Copy link
Member

est31 commented Sep 18, 2017

mod self::foo

That would be overly verbose and only support those people who want it gone IMO. mod foo is fine. It would also only confuse people who will then try to do mod self::foo::bar; and assume it works.

@rpjohnst
Copy link

If the main objection to the other RFC is that it contains too many pieces, why do the same thing in an attempt to change it?

Further, this is a lot of churn that a) has been brought up before and b) never got any consensus.

mod foo; is an item, and paths are what make up items, so requiring mod self::foo; conflates the two for no benefit- it's inconsistent with inline modules and non-module items, which all define a name rather than refer to one.

crate is a standin for "the current crate," and could easily have been the literal name of the current crate except for the lib/bin idiom. It's not referring to the src directory at all, and trying to make them match only introduces confusion like a) "can I use src::foo::bar if I name it src?" or b) "how can I refer to the crate directory in another crate?"

@nox
Copy link
Contributor

nox commented Sep 18, 2017

  • In any filesystem, the contents of a folder are inside that very folder, why should Rust modules behave differently?
  • You don't write struct self::Foo;, why should you write mod self::foo;?
  • cargo new creating crate/ is unneeded churn for a very marginal gain, and is inconsistent with dependencies, and most languages use src/ for their sources, let's not be different just for the sake of being different, thanks.
  • This RFC acts as if rustfix already exists. It doesn't.

The future of the module system itself will be affected by this change. If we are to be going to a "file-system" based module system, then I believe mod.rs is much easier to understand as the "root of the directory" than foo.rs + foo/

"File-system" based module system was rejected, no need to mention it again.

@rpjohnst
Copy link

"File-system" based module system was rejected, no need to mention it again.

While I agree with your other points, this isn't helpful. RFCs aren't set in stone, and there has always been the intention to open a new eRFC for file system-based modules discovery.

@vitiral
Copy link
Author

vitiral commented Sep 18, 2017

Okay, so people seem generally opposed to mod self::foo; and renaming src/ to crate/ (although I'm confused by the comment that it causes "code churn" as it is only the template that changes for new crates).

However, nobody has yet commented on the central point of the RFC: that we should not remove mod.rs because the new file-dir system changes the definition of self::. What is the response to that point?

@rpjohnst
Copy link

The other RFC doesn't remove mod.rs either. It just makes both foo.rs and foo/mod.rs work.

@vitiral
Copy link
Author

vitiral commented Sep 18, 2017

The other RFC doesn't remove mod.rs either. It just makes both foo.rs and foo/mod.rs work.

Having two ways of doing the same thing is arguably the worst of all worlds from a learnability perspective and is hardly in favor of that feature. I would honestly prefer the file-dir system than having both exist with a lint against neither.

On nightly behind a feature gate is fine, but once stabilized we should at least be able to decide one over the other.

@ExpHP
Copy link

ExpHP commented Sep 18, 2017

In every other context there is a consistent mental model for self:: and
super::, which is that it is the same as the "current directory" (./) (we
are ignoring inline modules). This is true if you are in foo/mod.rs or
foo/bar.rs. The file-dir model creates a special case where you can no longer
substitute self::foo for ./foo.rs in your head.

I am positively perplexed.1 I'm fairly certain that you were never able to substitute self::foo for ./foo.rs in your head.

$ tree src
src
├── main.rs
└── subdir
    ├── a.rs
    ├── b.rs
    └── mod.rs

1 directory, 4 files

src/main.rs

mod subdir;
fn main() { }

src/subdir/mod.rs

mod a;
mod b;

src/subdir/a.rs

pub struct S;

src/subdir/b.rs

use self::a::S; // error; should have used super::a::S
$ cargo run
   Compiling derp v0.1.0 (file:///home/lampam/cpp/derp)
error[E0432]: unresolved import `self::a`
 --> src/subdir/b.rs:1:11
  |
1 | use self::a::S; // error; should have used super::a::S
  |           ^ Could not find `a` in `self`

error: aborting due to previous error

error: Could not compile `derp`.

Footnotes

  1. By the entire premise, I mean. As far as I can tell, the entire quoted paragraph is false, unless we're working from subtly different definitions of terminology; but the wording seems pretty unambiguous to me!

@rpjohnst
Copy link

In lib.rs/main.rs and mod.rs files that contain mod foo; declarations, self::foo::bar does indeed refer to an item bar in the file ./foo.rs. You are correct that this does not work in other files.

Interestingly, early modules discussion proposed switching to Go's directory-based packages rather than Rust's file-based modules, which would have made this work everywhere. (This was rejected for making it too hard to find the definitions of items, and also for being too backwards-incompatible.)

@vitiral
Copy link
Author

vitiral commented Sep 18, 2017

Well... @ExpHP was correct in their analysis. It shows how confusing self:: and super:: paths are that I still don't seem to understand them properly. I thought I was finally getting it when I realized that they were just "relative". Apparently self:: just refers to the current file? I guess I'll never understand the point of that.

This is no longer a valid pull request, so I will now close.

@vitiral vitiral closed this Sep 18, 2017
@rpjohnst
Copy link

rpjohnst commented Sep 18, 2017

self:: is more useful in use paths as a way to change where the path starts. When implementing a facade, for example, you might use a bunch of stuff defined in submodules of the current module, in which case self:: can cut out a lot of redundant path segments that are liable to change if you ever move the module.

(As opposed to the more usual case of use, which is to refer to stuff from outside the current module, which won't move along with it.)

@ExpHP
Copy link

ExpHP commented Sep 18, 2017

@vitiral I believe it's easiest to understand the meaning of self and super when thinking entirely in terms of inline modules. You can think of the filesystem as a sort of "sugared" form of inline modules.

Here's how my example can be "desugared" into a single file:

src/main.rs

// at this level:
//  * `self`  is ::  (the crate root)
//  * `super` is invalid

mod subdir {
    // at this level:
    //  * `self`  is ::subdir
    //  * `super` is ::  (the crate root)

    mod a {
        // at this level:
        //  * `self`  is ::subdir::a
        //  * `super` is ::subdir
        pub struct S;
    }

    mod b {
        // at this level:
        //  * `self`  is ::subdir::b
        //  * `super` is ::subdir
        use self::a::S; // error; should have used super::a::S
    }
}

fn main() {}

Playpen link

@ExpHP
Copy link

ExpHP commented Sep 18, 2017

P.S. a common example of when self paths are useful:

mod tests {
    #[derive(Copy,Clone,Debug)]
    enum Color { Red, Green, Blue }

    // Import the variants so that we can write `Red` instead of `Color::Red`.
    // Notice that because `Color` is not defined in the crate's root module,
    // and because `use` paths are absolute by default,
    // we must explicitly `use self::Color::{...}` instead of `use Color::{...}`
    use self::Color::{Red, Green, Blue};
    
    #[test]
    fn test() {
        println!("{:?}", Red);
    }
}

Playpen

@vitiral
Copy link
Author

vitiral commented Sep 19, 2017

@rpjohnst @ExpHP Thanks for the clarifications, those helped.

@steveklabnik I'm not sure if you've heard this before, but my biggest confusion around self::/super:: is that self:: is apparently basically useless for pretty much anything except exposing a facade or doing @ExpHP's second example of breaking apart an enum.

The confusing thing about self:: is that you use it for items that are (kind of) already in your namespace. If I have a method on a struct and I use self.x, "self" refers to the object I am a part of -- it's not like x is already in my namespace! In order to do self::foo::a I must already have access to foo... it doesn't seem to be buying you anything, until you realize that it would be impossible to express some items without it.

Anyway, you being the docs guru I thought you might like to see this, since self:: + super:: will probably be gaining even more users after RFC 2126.

@thepowersgang
Copy link
Contributor

self has another edge-case use of referring to an item in the current module that is shadowed by something within the function (e.g. a variable)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants