-
-
Notifications
You must be signed in to change notification settings - Fork 3.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
Prelude and re-export cleanup #1247
Comments
I agree with this, although I think we should also include each sub crate as an item in the prelude too, and ensure everything is available from the roots of those crates That is, make PbrBundle accessible as |
I'm definitely open to this. Lets discuss as many alternatives as possible and try to get as many people to weigh in as possible. This is the kind of change I only want to make once, so I'm happy to move slowly on this 😄 @DJMcNab's idea is interesting. It does reduce the total amount of imports at the top, but such a pattern is basically encouraging people to prefix the module in front of every type they use (rather than importing the module and/or type at the top of a file). I'm personally an "import at the top" kind of person (with inline module paths to resolve conflicts and one-offs), but I'm curious what others think. I'm also a little worried that it would encourage imports in the style of |
I don't know which of those paths rust-analyzer would auto-import - it's worth experimenting. My suspicion is that it would do |
Another subtle user-induced bug caused by the huge glob imports: silently shadowing the |
Yes, happened also to me on bevy_prototype_lyon. I got it resolved when I started importing only the stuff I needed. That change made naming stuff way better. |
Regarding #1247 (comment):
Oh, have I got an opinion! 😄 Brief background to explain where I'm coming from. Professionally (i.e, not here) I've spent the last few years working in Go. Prior to that, I worked for about half a decade in Java. And before that I was working primarily in C++, which is closest to Rust out of these three, but didn't have a strongly established culture around naming (that may have changed, I haven't been following the various C++XX releases). The reason I bring this up is that Java and Go have very different approaches to imports, and I have a decent amount of experience with both of them, so I think I have grounds for comparison. JavaIn Java, there is no mechanism to import a package name -- you have to either provide a fully qualified name, based on DNS style roots ( Java also has a mechanism for star-imports (importing everything in a package into scope). At my workplace this was banned, and I believe this is common practice outside very rough and ready prototyping. The basic problem is that a star import makes it very hard for other engineers to understand where a name is coming from, or to predict which names will lead to collisions, or which names are available to their code. GoGo takes a very different approach to namespacing from Java. Go import declarations act on a package. By default, the name of the package is the last part of its fully qualified name. For instance, The Go community (at least, the parts of it I've interacted with) also has clearly established conventions around how to use their import mechanism. The references I usually cite to explain this are this blog post and this section of Effective Go. But the key point is that when designing APIs in Go, the name of the package is part of the name of the item, and the way your users will experience the name. This snippet from Effective Go shows how different it is from Java:
Another way to look at this is that talking about a Go identifier without discussing its package is like, eg, talking about a method without discussing the object to which it belongs. The Go also has an equivalent to star-import, Another Go specific technique (possibly specific to my workplace or even team) is the use of totally meaningless module imports, like RustI've only been writing Rust unprofessionally for about four months now, so I can't speak to its longstanding traditions, whatever they may be; I haven't found an equivalent of Effective Go or Effective Java for it. The module system, at least in terms of its handling to namespaces, is closest to C++: you can import both names and modules. Imports don't have to happen at any particular place in a file, either: you can This is good, in that it gives you the freedom to do whatever you want, but it's bad, in that you have to decide what the best thing is. My opinionThe Go recommendations regarding package names were surprising to me when I first encountered them, and I think they're surprising to most engineers who have previously worked in "more verbose" languages. When we bring on a new team member who isn't familiar with Go, they pretty much always are taken aback by how names are picked, and I have to walk through the rationales with them. But they work, and they work really well. There are parts of the Go language and community practices I don't like, but IMO they really nailed this aspect. Why do I like them? Module level imports are a great balance between (1) polluting your namespace with dozens of names, and (2) fully qualifying everything in the world. They make it clear which names are foreign without reading the list of imports. And if you aggressively strip out redundant parts of the names inside the module, they don't add boilerplate -- you just move parts of the name from the right side of the qualifier to the left. Finally, module level imports are helpful in that they remind readers about the structure and organization of the library they're coming from, making it easier to find related names and docs. Module level imports are sometimes a bit harder to design -- picking good names that are unambiguous without being redundant is a bit of an art. But following the general principle that code is read far more often than it's written, this is the right place to put the burden, rather than on the person trying to read a potentially unfamiliar code base. What I'm not sure about is whether this reasoning is applicable to Rust. As I said above, I'm fairly new to Rust and I don't have a sense of "common practice" yet. Unlike in Java, Rust programmers have the option of importing whole modules. But unlike in Go, they also have the option of importing type names. This feels simpler, and I worry that users of a crate might do it reflexively, without considering the impact on their experience. But it's possible that with clear guidance and docs, this won't happen. Practical notesMy codeMy pet project has a library that I've attempted to design as-if I was going to release it (who knows, maybe I will someday). I experimented with a couple of naming conventions, ranging from full qualification to full Java MassivelyUniqueVeryLongNames, and settled on a fairly Go style, where names are designed to be imported by their module -- with the exception that types named after the module are pulled directly into the namespace (so As a user of my crate, I find this really pleasant: I get to import as few names as possible, but the resulting code is still very readable and unambiguous. I have a But since I don't have any users besides myself, it's possible that I've just designed something that fits the quirks of my brain and no one else can understand it. 😄 Sample code from my rustdoc: use gridded::prelude::*;
use gridded::grid::square::{self, Square};
let sq = Square::new(10, 10);
let sq_right: square::Edge = sq.right();
let geom = square::Geom::relative_to_grid_origin();
// Invoke the trait method `outward_unit_normal`:
assert_eq!(
glam::vec2(1.0, 0.0),
geom.outward_unit_normal(sq_right)); Note: I've lately started compressing paths by re-exporting types named after the package "one level up". e.g, the code above could have imported BevyIn my use of Bevy, I went back and forth. I started out importing I then switched to fully qualified imports for everything, but this was way too much. My current standard Bevy header is: use bevy::ecs::prelude::*; // This has about the right stuff in it for the Query DSL
use bevy::ecs; and this works great. I only have one ECS in scope, so |
The current Bevy prelude is very large, and there's not much rhyme or reason to it. Stripping it down to a more minimal set reduces the difficulties with name space pollution (and anonymous imports), and keeps it more in line with standard Rust philosophy.
Proposed prelude list:
Related changes:
The text was updated successfully, but these errors were encountered: