-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Item-level blocks (was: Item-level *scopes*) #2377
Conversation
I think this is either a subset of #2128 or a small extension to it. Not quite sure which. |
I like this idea, if scoped modules have an actual scope, and that the items within have the visibility they provide, excluding 100% private items (i.e. anything not I think that item scope should be elaborated outside of attributes, because imho a solution which works exactly like Simple use case:
Scopes are useful here because the
while shadowing works if it's in a more specific scope, if those two things have the same scope, it'll error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite my individual comments, I think actual item-level scopes would be super useful in proc-macro
Derive
implementations, e.g. serde
uses a dummy const to introduce a scope for its extern crate
declaration and avoid polluting the parent module. I've recently had to do something similar in my own proc-macro
.
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const #dummy_const: () = {
extern crate serde as _serde;
#impl_block
};
This seems to be a very different use of the word "scope" than what this RFC proposes though.
text/0000-item-level-scopes.md
Outdated
use c; | ||
use d; | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example suggests that there won't be any actual scoping happening, it appears to be expecting the "scoped" use
statements will be exported to the parent scope.
For example, would this compile or be an error:
{
use a;
}
fn foo() {
a::foo();
}
If it is actually introducing scopes then I think an actual use of the use
s should be added to the examples to make them more like real code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually wait. I think the idea is that things inside the {}
are visible at the top-level.
So this should be possible:
#[cfg(target_os = "linux")] {
use a, b, c, d;
}
...
#[cfg(target_os = "linux")]
fn do_linux_things() {
a::blah();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I do see that there is an inconsistency. In regular Rust code, uses are scoped. So this doesn't work:
fn f () {
let _ = Path::new("lalala");
}
fn main() {
use std::path::Path;
f();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I think the new example on the other thread clarifies that this should be auto-exported to the parent scope. I wonder if there's some alternative phrasing for this that will reduce confusion on whether the items defined in the "item-level scope" are scoped to that block or available in the parent scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only other idea I have (which is less pretty) is:
#[cfg(...)]
parent! {
...
}
Where the purpose of parent!
is to make it explicit that the new scope is "injected" into the parent scope. But then that raises questions about whether such an interface is general:
fn f() {
{
parent! {
let x = 1;
}
println!("{}", x);
}
}
Should work?!
I don't see any need for such a use-case, so I'd favour not going down that path, but instead documenting that item-level scopes re-export to the top-level (module) scope. Full stop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By phrasing I was mostly referring to the documentation/naming of the feature. Mostly about removing the word "scope" from the name since visibility and scope are very tightly coupled terms. One alternative would be to focus on "block" instead, maybe "conditional item blocks"? Blocks are associated with visibility, but less so than scope.
text/0000-item-level-scopes.md
Outdated
} | ||
``` | ||
|
||
Which seems overly verbose and complicated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better example of a currently usable alternative is:
#[cfg(debug_assertions)]
use ::{a, b, c, d};
which I think is definitely good enough for this specific use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strongly agreed, this is what I was trying to say with my previous comment linking to #2128. If the only motivation for "item-level scopes" is to apply an attribute to several use
s, then your syntax as enabled by that RFC is the better solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends if people would like to (for example), batch a load of function defs and/or data structures into a guarded scope. Do you think that might be useful? If not, then perhaps this RFC is superfluous?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can imagine one such scenario: for platform abstractions, where each platform might have it's own data structures and functions.
#[cfg(target_platform = "linux")] {
use something::only::linux::uses;
use more::linux::stuff;
const SOME_SPECIFIC_PATH: &'static str = "/sys/blah/flibble/linux_specific123";
struct LinuxThingy1 {
...
}
impl LinuxThingy1 {
...
}
struct LinuxThingy2 {
...
}
impl LinuxThingy2 {
...
}
}
#[cfg(target_platform = "windows")] {
... lots of completely different imports and data structures...
}
#[cfg(target_platform = "freebsd")] {
... lots of yet different imports and data structures...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a much better example, another relevant example could be replacing the if_std!
macro used in futures
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would help there too.
But I'm a little spooked now, because as you said above, I didn't intend to introduce a "real scope" but users may expect one because of the "{ ... }".
This is something that is supposed to be done through macro hygiene and not module-level blocks. |
Regarding the syntax, it might be a good idea to sell item-level scopes as "anonymous modules": #[cfg(debug_assertions)]
mod {
// ...
} This clarifies it as an item-level scope and allows easier visual recognition of the block start, since It also has precedent with C++'s concept of anonymous namespaces, where |
@fknorr I'm not sure about spinning it as an anonymous module. The items inside a item-level scope should be visible at the top-level of the module, whereas a sub-module would have its own name-space. So your suggestion implies that we special case item-level scopes? |
Welcome and thank you for contributing to the design of Rust! With respect to process, various people will comment, etc. Currently, the language team is primarily focused on urgent RFCs that need to be settled before the new edition. Eventually however, the language team will discuss this RFC in a meeting and they will ask for changes or someone in the team will propose to merge / postpone / close the RFC. |
text/0000-item-level-scopes.md
Outdated
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
An Item-level scope is allowed wherever an Item may appear. Item-level scopes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe as currently framed, this RFC constitutes a breaking change.
Consider for example the following snippet which is valid today:
fn main () {
#[cfg(debug_assertions)] {
struct X;
}
#[cfg(debug_assertions)] {
struct X;
}
}
Here, the struct X
s are items, but my understanding of this RFC is that struct X
would both be exported to the parent scope (fn main() { .. }
) and thus cause a conflict for existing code which the compiler doesn't know how to resolve.
The mod { .. }
idea does not have this problem tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The blocks that I'm proposing appear only at the top level of a module. The semantics of your above example should be untouched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true; However, it may cause confusion between the semantics of top level items and items inside functions. Meanwhile, mod { .. }
as anonymous modules do not cause such confusion.
@vext01 exactly, but I can see where that might cause confusion with 'real' modules. It might be surprising though that inside a function, OTOH, it's not really consistent how curly braces interact with scoping anyway. When defining an item or inside a function they introduce a namespace, but then we have |
Another piece of code I have in one of my projects is:
Is anyone aware of a more concise way to write this with current Rust, or is this another good use-case for this RFC? |
Plus minor tweaks.
Thanks for everyones input. I've revised the RFC to include your comments. |
I feel a bit nervous about introducing blocks -- there is definitely the potential for confusion about the scoping. My inclination for the scenarios covered in this RFC is usually to make a module -- one could also imagine writing a macro (e.g., Perhaps the @rust-lang/libs team would like to weigh in with their experiences from libstd. |
I have a lot of experience from writing no_std compatible libraries (failure and futures) where the current best practice is to have a I think better than the status quo but also possibly better than just introducing arbitrary blocks is adding a built-in (someday a macro with the two-block syntax) like |
One thing I just remembered is there is one place that the I don't think this RFC as written could be used in that position, I'm not really sure whether the scope inside an |
In the interests of moving this forward: It sounds like the primary concern here is whether people will expect things declared inside a On the flip side, I do think the ability to have a block and apply attributes to it (starting with Given that, I do think it might make sense to wrap this in a built-in macro, taking the attribute (using standard attribute syntax) and the block as arguments. |
I would like the solution to allow scoping {
use some_crate::*;
fn i_use_tons_of_stuff() { ... }
}
fn i_dont() { /* can't accidentally refer to some_crate here */ } C++ provides us a little precedent here with anonymous namespaces, which are used largely for visibility, and provide an effect similar to implicitly declaring them at a previous level. Things declared inside the anonymous namespace, though not accessible from other translation units, act similarly to how they would if they were declared at the top-level. That said, C++ also now has inline namespaces, where However, bringing declarations up a level would also mean that you can't scope declarations to the block alone; they would necessarily be visible from the rest of the module (and thus from children). This feels a little bit unclean. To throw another bike at the shed, what if any item declared in an anonymous module with a mod {
pub(self) fn f() {}
fn h() {}
}
fn g() { f() } // ok
fn f() {} // not ok, f is already declared in this scope
fn i() { h() } // not ok, h is not visible The downside is the boilerplate, of course; since Rust has no way to explicitly make something less public, this becomes tricky. |
Based on discussions among the language team, we mostly agreed that we'd like a way to reduce repetition of the same For that reason: However, we'd love to see a proposal that does not use braces, and still eliminates repetition, such as an attribute alias system, or some other syntax. |
Team member @joshtriplett has proposed to close this. The next step is review by the rest of the tagged teams:
No concerns currently listed. 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. |
I'm not sure I agree with the sentiment that |
Oppose. This is what inline mods are there for. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to close, as per the review above, is now complete. By the power vested in me by Rust, I hereby close this RFC. |
Hi,
This is a draft PR to address #2312.
rendered
This is my first contribution to this repo, so I may require some guidance WRT the process.
Fixes #2312
Thanks