Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upStabilize uniform paths on Rust 2018 #55618
Comments
withoutboats
added
the
T-lang
label
Nov 2, 2018
This comment has been minimized.
This comment has been minimized.
|
@rfcbot fcp merge |
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Nov 2, 2018
•
|
Team member @withoutboats has proposed to merge this. The next step is review by the rest of the tagged teams:
Concerns:
Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
rfcbot
added
proposed-final-comment-period
disposition-merge
labels
Nov 2, 2018
This comment has been minimized.
This comment has been minimized.
|
@withoutboats Thank you for your detailed writeup, as well as your careful and considered evaluation. Having been the sole opposition of more than one language proposal in the past, I would like to explicitly extend the following invitation to @cramertj: If you feel you need more time to write up some thoughts on how |
This comment has been minimized.
This comment has been minimized.
It's good to say it explicitly, of course, but -- to be clear -- I feel this is always true for any FCP =) |
This comment has been minimized.
This comment has been minimized.
|
Excellent writeup @withoutboats. Report & Tests@rfcbot concern stabilization-report-and-tests Before we stabilize File-centric behavior@rfcbot concern file-centric-behavior As for what we're going to stabilize here, @nikomatsakis noted in the paper document that a more file-centric adaptation of use std::sync::Arc;
mod foo {
mod bar {
// uses the `use` from parent module, because it is in the same file
fn baz() -> Arc<u32> { ... }
}
}BenefitsThe file-centric approach is beneficial mainly because it is quite common for languages to take such an approach and Rust is the odd fish in the bunch; By adopting an approach where the above snippet is legal, the speed-bump when learning Rust can be reduced. For example, in Java, inner classes can see the imports at the top of the file. Another benefit of the file-centric approach is that you no longer have to write the use some_crate::Thing;
fn foo(x: Bar) -> Baz { ... }
#[cfg(test)]
mod test {
// use super::*;
// ^-- you don't have to do this anymore.
...
}DrawbacksI see two principal drawbacks with the file-centric behavior:
One mitigating factor here is that it is unlikely to have inline modules which nest; the most common use of inline modules are for unit tests or for small modules that contain platform specific behavior -- in the language team meeting, everyone agreed that this was the case and that nested inline modules were not commonly used. ConclusionAll in all, while I was initially somewhat skeptical about the file-centric behavior, I think it is in line with one of the core goals of Why does it have to happen now? Consider the following (playground): // Nightly, Edition 2018 on.
#![feature(uniform_paths)]
// Let's assume that there's a crate `foo` in the extern prelude with `foobar` in it.
mod foo {
pub fn foobar() -> u8 { 1 }
}
mod bar {
fn barfoo() -> u8 {
// With the current `uniform_paths`,
// there's no conflict as far as the resolution system sees it.
//
// Thus, if `foo` is in the extern prelude, it will resolve to the
// `foo` crate and not `foo` the sibling module. If we allow that
// to pass, then a *breaking change* will be the result if we change
// to the file centric behavior.
foo::foobar()
}
} |
This comment has been minimized.
This comment has been minimized.
lucozadeez
commented
Nov 2, 2018
|
Apologies if this has already been considered but would So the logic would always be
It has the advantage of using an existing keyword. I couldn't see any reason for there to be a parsing ambiguity but I could be wrong there. |
This comment has been minimized.
This comment has been minimized.
To disambiguate, you can write
I don't think there would be any ambiguity. |
Centril
referenced this issue
Nov 2, 2018
Open
(Modules) Tracking issue for Picking a Module Path System variant #53130
This comment was marked as resolved.
This comment was marked as resolved.
|
@Centril I feel that the "file-centric approach" is something that could be introduced as an extension later, with sufficient care. And as @withoutboats put it, "Any third proposal which is not completely backwards compatible with the current behavior of paths in Rust 2018 is impossible to ship in Rust 2018 at this point. Variations on anchored or uniform paths that are backwards compatible may be considered, but that also seems unlikely." As such, please consider whether the "file-centric approach" could be written as a compatible add-on. |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
Implementation is in progress, so the behavior will change slightly. I'd prefer to test the new implementation for one more cycle and not backport stabilization to 1.31. |
This comment was marked as resolved.
This comment was marked as resolved.
|
So @Centril points out that extending to support "all enclosing scopes within the file" would not, strictly speaking, be backwards compatible (without some sort of fallback or prioritization, I guess). For example this would compile today but yield ambiguity tomorrow: mod rayon { ... }
mod foo {
use rayon::join; // today, compiles to extern crate `rayon`
}Similarly, this builds today: #![feature(uniform_paths)]
mod bar {
pub fn x() { }
}
fn main() {
mod bar { pub fn y() { } }
use bar::x;
x();
}Regardless, I'm not inclined to block much longer on this stuff. Que sera sera. Gotta ship someday. =) |
This comment was marked as resolved.
This comment was marked as resolved.
|
Good point and I suppose this is the cause for their "concern". |
This comment was marked as resolved.
This comment was marked as resolved.
|
I would be OK with skipping the It seems to me that instituting changes to |
This comment was marked as resolved.
This comment was marked as resolved.
So, my suggestion would be to pursue this in some opt-in form after the edition is released. Then if it's implemented, 2-3 years of usage experience should be enough to decide whether it should be enabled by default in some cases in Rust 2021 or not. |
This comment was marked as resolved.
This comment was marked as resolved.
So I take it you don't think it can be feasibly implemented in the short period remaining until Edition 2018 ships?
I don't think that would be worth it and it would just be even more technical debt; the idea is that it should be intuitive by default... using that attribute wouldn't be.
This makes a lot of sense, so it seems like the file-centric idea might need some design work + process given that.
I suppose that the file-centric approach isn't very actionable right now and neither are the forward-compatibility hacks to make it possible later... I think we might just have to either use forward-compat warnings later (and implement them mid-edition-2018), or do it in Rust 2021 (tho the technical debt isn't great...). Therefore... |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats You're blog post is really helpful. Thanks! Personally, I've been using edition 2018 beta/nightly for while now, and I haven't really run into
|
This comment has been minimized.
This comment has been minimized.
|
After reading @withoutboats post, I wanted to make sure the following compromise has been considered and rejected: to do uniform paths, but always requiring the leading By disambiguating-by-default, the two disadvantages listed in the blog post get neutralized:
At the expense of undoing the initial advantage that This seems like an elegant compromise to me after reading the blog post, but I fully realize it probably has already been discussed to death. Just wanted to throw this out there and maybe allow someone from the lang team to articulate why this alternative is less attractive than uniform-paths as proposed. |
This comment has been minimized.
This comment has been minimized.
|
I would personally really not like to write or read |
This comment has been minimized.
This comment has been minimized.
|
Could someone please remind me how the following would work for uniform and anchored paths? // Assume there is also an
// extern crate foo;
mod foo;
fn main() {
// Does this call the module or the crate? (Or is it an error?)
foo::bar();
// How would I call the other?
}Edit: Also, would |
This comment has been minimized.
This comment has been minimized.
tommit
commented
Nov 3, 2018
•
|
Whichever path design you choose, I'd wish everybody wrote use crate::{
thismod::foo::Foo,
// ...
};But if you choose the uniform paths design, then there will probably be some who write their imports using absolute paths and some using relative paths, and it's the readers of the code who suffer from this mishmash of styles. So, I would definitely prefer you choose the anchored paths design, but I'd make one change, or addition to it: I'd allow you to start the path in a use declaration with the name of an enum type that is currently in scope. This would provide a nicer syntax for importing enum variants (typically in function local scope) by simply: |
This comment has been minimized.
This comment has been minimized.
tommit
commented
Nov 3, 2018
•
I just tried that out, and in both uniform and anchored paths designs, your code calls the module function. You can unambiguously call the module one with But what's more interesting is that this is a really bad behaviour. The act of adding a module should not silently hijack a function coming from an external crate. I think that is one of the main design principles of how imports are designed in the D language. EDIT: I just re-read about how imports work in D language (which I had mostly forgotten about). The main design principle of imports in D says that: "Adding or removing imports can't change the decision of resolving a function name". Where "resolving a function name" means: "figuring out which function to call when the programmer writes This is so different from how things work in Rust, that I seem to be unable to compare the two, probably due to not knowing exactly how this stuff works in either language. But in Rust specifically, an external crate's name seems to get hidden by any other symbol in scope, including symbols coming from glob imports. For example, given the Cargo dependency use crate::sub::*;
mod sub {
// This, whether from a glob import or regular import, hides the extern crate `num`
pub mod num {
pub fn one() -> u64 {
2
}
}
}
// This hides both the extern crate `num` and `crate::sub::num` coming from glob import
mod num {
pub fn one() -> u64 {
3
}
}
fn main() {
let x: u64 = num::one();
println!("x: {:?}", x);
}I realize that this kind of hiding behaviour is not the same thing as the "function hijacking" which D language's import rules are designed to prevent: a function that's imported from one module hijacking another function with the same name that's imported from a different module. That kind of hijacking is not possible in Rust either. In the example above, it's just a matter of a "more local" symbol hiding a "less local" or "more external" symbol. I think this is fine, and in fact D language does that kind of thing as well - an imported symbol gets hidden by a module local symbol. |
This comment has been minimized.
This comment has been minimized.
|
I would be shocked if the “file centric approach” was accepted. This is a major new thing that would be stabilized almost immediately without RFC-like discussion. I don’t like to speak too strongly but I’m shocked it was even suggested. In terms of “anchored” vs “uniform”, I still prefer anchored, for basically all of these reasons: https://www.reddit.com/r/rust/comments/9toa5j/comment/e8xxs2p I have a bit more to say but I’m about to catch a flight; in the end, I’m not willing to die on the hill of anchored paths, but do prefer them quite strongly. |
This comment has been minimized.
This comment has been minimized.
|
In the discussions submodules and disambiguation are often taken into account, but I'm not seeing much consideration of workspaces, so here's my use case. In projects I write I use workspaces a lot. I have a natural progression for chunks of code:
For example, see how many "internal" crates crates.rs has: https://gitlab.com/crates.rs Currently, refactoring this way is actually convenient as there is no syntactic difference between modules and crates. I can replace AFAIK anchored paths variant intentionally removes that ability, which is why I don't like it. Not only I'm not interested in marking whether something is a module or extern crate, I deliberately don't want that distinction and take advantage of modules and crates having uniform syntax. |
This comment has been minimized.
This comment has been minimized.
Thanks for linking to this comment, I think it succinctly sums up the most common arguments in favor of anchored paths. But I must say, this summary reveals what seems to me to be a flaw in understanding about how paths work in the anchored paths proposal (or indeed, in any version of Rust that exists): the post claims quite emphatically that the appeal is that you "ALWAYS" anchor paths, but this is not true: you only anchor paths in All but the last argument in this comment (which is a claim about Rust's philosophy I don't agree with) seem to fall apart when you consider that even under anchored paths, paths sometimes have the uniform path semantics. You are still subject to the same refactoring hazards, you still have to understand the contextual information, its just not true in the case of This is why uniform paths is called uniform paths: it makes this semantics true everywhere, instead of having a separate logic that only applies to one item form. |
This comment has been minimized.
This comment has been minimized.
tommit
commented
Nov 4, 2018
•
Both of the new path variants remove that ability inside modules. The uniform paths variant retains that ability only in the current crate's root, which isn't usually where most of the code is. |
This comment has been minimized.
This comment has been minimized.
This works fine and is even preferable in my opinion; since its only used for disambiguating, being kinda long isnt the problem, and However, I think we're currently using
I'm fairly certain we've discussed this and ultimately rejected it. I see this as the third of the three potentially viable variants on the 2018 path changes, each of which have two of these three desirable properties:
We've already decided that the third bullet is desirable, and its been a question between the other two bullets, which is what anchored and uniform represent. More broadly, I want to point out that while having ambiguity might sound troubling, the reality is that name resolution is already full of ambiguity, for example, glob imports and the prelude introduce potential name conflicts which are partially solved through shadowing rather than disambiguation. |
This comment has been minimized.
This comment has been minimized.
phaylon
commented
Nov 8, 2018
•
|
@rpjohnst I'm interested in what the current module/file is connected to. But as said above, that's another thing that a lint can fix easily, since writing down the imports is still possible. |
This comment has been minimized.
This comment has been minimized.
|
Based on discussions in the lang team meeting, we observed that it is the de facto standard (rustc's own code notwithstanding) to use separate blocks of Speaking with my style team hat on, I think we should add that de facto standard to the style guide. |
This comment has been minimized.
This comment has been minimized.
|
@cramertj can you mark your idiom concern as resolved? |
This comment has been minimized.
This comment has been minimized.
|
@rfcbot resolve idiomatic_use General consensus is that idiomatic use of this feature requires users to separate items being imported from the current module from items imported from external crates, either through a separate unform_paths gets us closer to the one-path vision, and I'm looking forward to seeing a new generation of Rustaceans who aren't confused by the module system ;) |
This comment has been minimized.
This comment has been minimized.
|
@rfcbot concern implementation I wanted to log the concern raised by @petrochenkov on Discord:
Given that the edition release is 4 weeks away, and we should have artifacts ready in probably 2 weeks time, I feel like the above is probably a show-stopper for moving to uniform paths at the Edition release, and we should hold off until the next cycle. Thoughts? |
This comment has been minimized.
This comment has been minimized.
We have the future compat bits in place so if this has to wait until the next cycle it has to wait until the next cycle. Better safe than sorry! |
This comment has been minimized.
This comment has been minimized.
We don't have all the future compat bits in place - that's the point, so the reimplementation is going to land on the current beta (next week, most likely), but it's somewhat risky to stabilize it immediately after that without testing on complex crates with macros, globs and everything (e.g. Servo). |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov right, OK; so we should get all the future compat bits in place and then wait a bit until you are comfortable with the implementation having been tested by Servo and other crates. I'm down with that. I think we can defer the appropriate moment to stabilize to you. :) (and we need the stabilization report done anyways...) |
petrochenkov
referenced this issue
Nov 12, 2018
Merged
[beta] resolve: Implement uniform paths 2.0 #55884
This comment has been minimized.
This comment has been minimized.
|
Reimplementation PR - #55884. |
This comment has been minimized.
This comment has been minimized.
|
Just in case, if something goes wrong with enabling uniform paths by default (e.g. too many "resolution is stuck" errors), then it's always an option to turn on the in-scope resolution on demand with something like fn f() {
enum E { A, B }
use in::E::*;
match A {
A => {}
B => {}
}
}are still possible to write. |
added a commit
that referenced
this issue
Nov 13, 2018
added a commit
that referenced
this issue
Nov 14, 2018
This comment has been minimized.
This comment has been minimized.
Should hold off edition 2018 until next year, and rename it to edtion 2019. Not just uniform paths, we have other important features not completed, e.g. async/await. |
This comment has been minimized.
This comment has been minimized.
|
I was under the impression that boat has already sailed. Either way, the only thing that really needs to be in 1.31 is any breaking changes that can't be done except across an edition boundary. |
This comment has been minimized.
This comment has been minimized.
|
So it seems that language team is pretty settled with choosing uniform paths? I still prefer anchored paths, but if the decision is made, then I guess that's fine. Mostly, it would be nice to get an official confirmation that that's still how the language team is leaning. |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m the position of the language team is pretty well tracked by RFCbot's post, which shows all but one member have checked to approve the proposal & the only concerns are about implementation and testing. I think your comment might come more from a sense that more people on this thread spoke in favor of anchored paths than uniform paths. That's my impression of the situation, numerically at least. But that's not surprising: people usually comment when they disagree with a decision we're making, since there is little need to comment when it seems like what you would be trying to make happen is already going to happen. So I at least tend to only consider the arguments, and not the "mood of the thread," when making decisions. None of the arguments led me to have concerns about uniform paths (and remember, I was on the anchored paths side until very recently). In fact, most seemed confused to me (as I tried to express several times), since they were objecting to the fallback mechanism in itself, which is a part of both proposals, but only relevant outside of |
added a commit
that referenced
this issue
Nov 17, 2018
added a commit
that referenced
this issue
Nov 18, 2018
added a commit
that referenced
this issue
Nov 18, 2018
This comment has been minimized.
This comment has been minimized.
ErichDonGubler
commented
Nov 21, 2018
|
I see that @steveklabnik actually linked to my comment from Reddit! Woot! Since I haven't seen much response to that, I'll inline the points from that link here in hopes that they can be addressed. I'm one of the people firmly in the camp of the anchored paths variant. "Why?", you might ask. Well:
I'm sure that my points aren't necessarily original, and that rebuttals may already exist for them. I'd love to hear them! :) |
This comment has been minimized.
This comment has been minimized.
|
@ErichDonGubler I responded to your original comment here. Your comment really pulls out the contradiction that I don't understand in the argument in favor of anchored paths (referenced here, the most recent comment before yours):
The whole argument for uniform paths is that they're consistent everywhere - that's why its called uniform paths! Specifically, anchored paths are not consistent between the behavior of paths outside of use statements and in use statements. My impression from your post is that you didn't read the thread. I know that its a big time investment, but in future please at least try to do that instead of just making a post that may be repetitious of previous comments; its about respecting everyone else's time. |
This comment has been minimized.
This comment has been minimized.
ErichDonGubler
commented
Nov 21, 2018
•
You're right. That embarassing, especially given that your response to Steve's comment is the second message after it. I apologize -- please let me try to fix that mistake! To your responses:
As you've indicated, I think the vast majority of my misunderstanding comes from the fact that I didn't even think that uniform path handling is what will be used to handle non- Erich thinking out loud. Feel free to ignore this.So, if I understand right, these snippets are correct: // src/random_module.rs
// This is how I normally use Rust 2015 when it comes to importing things.
// List everything that comes from other modules/deps as a "`use` manifest".
//
// The whitespace for the `use`s below isn't normal Rust style -- I actually prefer to use
// this style, but I'm only using it here for demonstrative purposes.
//
// None of this `use` statement would really change with Rust 2018, IIUC.
use {
self::sentience::think_deeply,
super::{
try_frobicate,
FrobnicateError,
},
std::fmt::{
Debug,
Formatter,
Result as FmtResult,
},
};
pub(crate) mod sentience {
// Another "`use` manifest", as it were.
// Note that `shallow_thoughts` could be better disambiguated in Rust 2018 with:
// use crate::shallow_thoughts;
use {
::shallow_thoughts,
std::mem::transmute,
};
pub(crate) struct DeepThoughts { /* ... */ }
pub(crate) fn think_deeply() -> DeepThoughts { // No need to use `self::...` here
let mut stuff = shallow_thoughts();
// ...
let stuff_bytes: [u8; 4] = unsafe {
transmute(stuff);
};
// ...
}
}
pub enum ProcessError {
FrobnicateFailed(FrobnicateError),
}
impl Debug for ProcessError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
// ...
}
}
fn process() -> Result<(), ProcessError> {
use self::ProcessError::*; // Needs `self::...` because this is a `use` statement
let mut deep_thoughts = think_deeply();
try_frobnicate(deep_thoughts).map_err(FrobnicateFailed)?;
Ok(())
}If you look at the code I wrote above, you might note that the flow I prefer totally avoids non- ...dang it, did I just become convinced that uniform paths are the way to go?
This is off-topic, so feel free to reply somewhere else, but I'm genuinely interested in how your vision of Rust differs from how I expressed it. :) I understand that your time might be better spent elsewhere, but I'm curious! |
withoutboats commentedNov 2, 2018
@rfcbot fcp merge
cc #53130
This issue is to track the discussion of the following proposal:
We've taken informal polls about this question in a paper document. Most of the lang team has favored uniform_paths, I and @cramertj were the only two members who initially favored anchored_paths. Yesterday, we discussed this in the lang team video meeting; in the meeting we sort of crystallized the various pros and cons of the two variants, but didn't reach a consensus.
In the time since the meeting, I've come around to thinking that stabilizing on uniform_paths is probably the best choice. I imagine @cramertj has not changed his mind, and though he said in the meeting he wouldn't block a decision, I hope we can use this FCP period to see if there's any agreeable changes that would make uniform_paths more palatable to him.
I've also written a blog post summarizing the meeting discussion and how my position has evolved, which provides more context on this decision.
I also want to make sure we continue to get community feedback as we reach this final decision! Please contribute new thoughts you have to the discussion. However, we will hopefully make this decision within the next few weeks, and certain proposals are out of scope. Any third proposal which is not completely backwards compatible with the current behavior of paths in Rust 2018 is impossible to ship in Rust 2018 at this point. Variations on anchored or uniform paths that are backwards compatible may be considered, but that also seems unlikely.