-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Stabilize uniform paths on Rust 2018 #55618
Comments
@rfcbot fcp merge |
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. |
@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 |
It's good to say it explicitly, of course, but -- to be clear -- I feel this is always true for any FCP =) |
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()
}
} |
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. |
To disambiguate, you can write
I don't think there would be any ambiguity. |
This comment has been minimized.
This comment has been minimized.
😍 Yes please! 😍 |
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 has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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
|
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. |
I would personally really not like to write or read |
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 |
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: |
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. |
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. |
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. |
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. |
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 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. |
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! :) |
@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. |
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! |
Some follow-up issues:
|
253: Fix diagnostic fixes showing up everywhere r=matklad a=flodiebold The LSP code action request always returned the fixes for all diagnostics anywhere in the file, because of a shadowed variable. There's no test yet; I wasn't sure where to add it. I tried adding one in `heavy_tests`, but that's a bit uncomfortable because the code action response contains the (random) file paths. I could make it work, but wanted to ask beforehand what you think. 256: Improve/add use_item documentation r=matklad a=DJMcNab Adds some documentation to use_item explaining all code paths (use imports are hard, especially with the ongoing discussion of anchored v. uniform paths - see rust-lang/rust#55618 for what appears to be the latest developments) Co-authored-by: Florian Diebold <flodiebold@gmail.com> Co-authored-by: DJMcNab <36049421+djmcnab@users.noreply.github.com>
256: Improve/add use_item documentation r=matklad a=DJMcNab Adds some documentation to use_item explaining all code paths (use imports are hard, especially with the ongoing discussion of anchored v. uniform paths - see rust-lang/rust#55618 for what appears to be the latest developments) Co-authored-by: DJMcNab <36049421+djmcnab@users.noreply.github.com>
@rfcbot resolve implementation EDIT: Apparently @aturon should do this to mark the concern in #55618 (comment) as resolved. |
@rfcbot resolve implementation |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Stabilize `uniform_paths` Address all the things described in #56417. Assign visibilities to `macro_rules` items - `pub` to `macro_export`-ed macros and `pub(crate)` to non-exported macros, these visibilities determine how far these macros can be reexported with `use`. Prohibit use of reexported inert attributes and "tool modules", after renaming (e.g. `use inline as imported_inline`) their respective tools and even compiler passes won't be able to recognize and properly check them. Also turn use of uniform paths in 2015 macros into an error, I'd prefer to neither remove nor stabilize them right away because I still want to make some experiments in this area (uniform path fallback was added to 2015 macros used on 2018 edition in #56053 (comment)). UPDATE: The last commit also stabilizes the feature `uniform_paths`. Closes #53130 Closes #55618 Closes #56326 Closes #56398 Closes #56417 Closes #56821 Closes #57252 Closes #57422
Stabilize `uniform_paths` Address all the things described in #56417. Assign visibilities to `macro_rules` items - `pub` to `macro_export`-ed macros and `pub(crate)` to non-exported macros, these visibilities determine how far these macros can be reexported with `use`. Prohibit use of reexported inert attributes and "tool modules", after renaming (e.g. `use inline as imported_inline`) their respective tools and even compiler passes won't be able to recognize and properly check them. Also turn use of uniform paths in 2015 macros into an error, I'd prefer to neither remove nor stabilize them right away because I still want to make some experiments in this area (uniform path fallback was added to 2015 macros used on 2018 edition in #56053 (comment)). UPDATE: The last commit also stabilizes the feature `uniform_paths`. Closes #53130 Closes #55618 Closes #56326 Closes #56398 Closes #56417 Closes #56821 Closes #57252 Closes #57422
I'm confused. Shouldn't this issue remain open until the FCP completes? |
@ErichDonGubler well, due to time constraints it will already be in 1.32 and FCP in #56759 (comment) will complete in 1 day. @rfcbot cancel |
Finally, uniform path was chosen instead of anchored paths (see rust-lang/rust#55618), so we don't need inspection related to anchored paths
3767: INSP: drop RsAnchoredPathsInspection r=Undin a=Undin Finally, uniform paths were chosen instead of anchored paths (see rust-lang/rust#55618), so we don't need inspection related to anchored paths Co-authored-by: Arseniy Pendryak <a.pendryak@yandex.ru>
Finally, uniform path was chosen instead of anchored paths (see rust-lang/rust#55618), so we don't need inspection related to anchored paths
@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.
The text was updated successfully, but these errors were encountered: