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

Automatically Usable External Crates #2088

Closed
wants to merge 7 commits into from

Conversation

cramertj
Copy link
Member

@cramertj cramertj commented Jul 28, 2017

This RFC reduces redundant boilerplate when including external crates. With this change, projects using Cargo (or other build systems using the same mechanism) will no longer have to specify extern crate: dependencies added to Cargo.toml will be automatically useable. We continue to support extern crate for backwards compatibility with the option of phasing it out in future Rust epochs.

Rendered

@cramertj cramertj added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jul 28, 2017
# Cargo.toml:
name = "my_crate"
version = "0.1.0"
authors = ["Me" <me@mail.com>]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this line confuses Github's rendering.

external dependency appears anywhere within the crate.
For example, if `rand = "0.3"` is listed as a dependency in Cargo.toml
and `extern crate rand;` appears somewhere in the crate being compiled,
then no implicit `extern crate rand;` will be added.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this special case? It seems like an "if a tree falls in the forest" kind of thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal preference would be that users avoid extern crate as much as possible. In a world with no extern crate, this rule doesn't really matter at all.

I know that there are other people who will disagree, though, and I think that this check will allow them to continue keeping a tight lock on where their external dependencies are used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this portion of the RFC is under-specified, and could also use a bit more analysis around rationale.

re: under-specification: "appears anywhere within the crate" is a bit ambiguous. Are we taking cfg into account? Macros?

re: rationale: you mention back-compat and wanting to control scoping. It might be useful to separate those concerns a bit. Is there something more minimal we could do strictly for back-compat?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is actually impossible to check for „anywhere within the crate“. Or, if I have an example (file in examples) using the library and that one uses an external crate, does it count? How does the rustc compiling the library know that, without being informed about the examples? Or does a crate mean one unit of compilation, therefore the example would be considered a separate crate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "anywhere within the crate" I meant "anywhere within the crate source currently being compiled after macro expansion" (including cfgs). This isn't perfect, but I think it's the only truly viable option. I suppose in order for macros to be imported correctly in a macros 2.0 world, you'd have to already know at least some set of external crates that you were going to import. I'll have to think about this-- my gut reaction is to say that external macros expanding to extern crate; could be made an error, but maybe that's too drastic / surprising.

Overall, I feel like I'm pretty open to suggestions on this front. My goal was to support backcompat and leave an "out" for people who had special use cases for extern crate-- perhaps I should have instead focused merely on backcompat.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. That dependency on cfgs, that can make a crate to auto-include or not, might be a source of unexpected failures on different configurations.

#[cfg(a_feature)]
mod submodule {
  extern crate a_crate;
}

fn main() {
  a_crate::do_something();
}

This will stop compiling if the a_feature is activated, but otherwise will compile just fine. And the a_feature can be hidden somewhere deep.

I don't have any new proposal (I think there are some in the discussion), but I find this behaviour a bit complex and magical.

Copy link

@le-jzr le-jzr Jul 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cramertj For backcompat, just leave extern crate working as it does now. Even currently, it's not an error to have extern crate in the root and then also extern crate in several submodules. With the RFC, extern crate lib; and use lib; should be mostly interchangeable in their basic forms.

Copy link

@le-jzr le-jzr Jul 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation for extern crate anywhere disabling the feature is just about not polluting namespace. We've already established that the RFC shouldn't result in automatic inclusion in local namespace. It can possibly be further specified that the automatic path links can be shadowed by root modules (which would make the shadowed crates only accessible via extern crate, but that's not a problem, and it makes everything work backwards compatibly).


- Don't do this.
- Specify external dependencies using only `extern crate`, rather than only
`Cargo.toml`, by using `extern crate foo = "0.2";` or similar. This would
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the olden days, the pre-Cargo build tool behaved kind of like this, interestingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do something even simpler. If cargo finds a missing dependency, it can offer to add the latest version from crates.io to Cargo.toml (pinning it to ^latestVersion).

Creating a root-level item named `std` will prevent `std` from being included,
and will trigger a warning.

It will still be necessary to use the `extern crate` syntax when using
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is actually kind of a drawback - if there's a long enough period where this RFC is implemented but macros 2.0 isn't, then newcomers to the language won't know about extern crate at all until they want to use a macro. Hopefully that gap won't be all that long though?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's definitely annoying. Since we do have a plan to migrate away from #[macro_use] in the relatively near future, I think it's livable, but the overlap period of "extern crate just for macros" is frustrating.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub extern crate would also still have to be explicit, I guess.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub extern crate could potentially be replaced with pub use crate. This could cause problems with having the same item imported twice though.

@sfackler
Copy link
Member

Big 👍 from me. This is especially nice for target-specific dependencies and dev dependencies. Usage of those crates will "just work" in contexts that are already conditionally compiled instead of having to pepper the crate root with a bunch of cfg'd extern crates.

@sfackler
Copy link
Member

Not sure if it needs to be called out in the RFC specifically, but it'd be good to update the unused extern crate lint to to work for implicit extern crates as well so you know if you're depending on a crate you no longer use.

@eddyb
Copy link
Member

eddyb commented Jul 28, 2017

Small nit-pick: I would use "deduce" instead of "infer" (or, alternatively, something along the lines of "automatic extern crate declarations from Cargo dependencies").

Inference, in its generality, would mean e.g. having use foo::bar; somewhere with foo not a module in the current crate, would result in extern crate foo; being injected, which isn't what's being proposed here.

In fact, it's "simply" a different way to specify dependencies (via the command-line), which we perhaps should have done a long time ago. We used to have extern crate foo = "..."; as a syntax, I believe, which, if Cargo integrated with rustc tighter, could be used instead of Cargo.toml, e.g.:

extern crate rand = "*";
extern crate glutin = "https://github.com/tomaka/glutin#next";

What we have right now in stable Rust is the most boring/verbose approach: a list of dependencies inCargo.toml, combined with a manual list of extern crate. The latter is purely vestigial IMO.

`extern crate` in order to have more fine grained control-- say, if they wanted
to import an external crate only inside an inner module.
No automatic import will occur if an `extern crate` declaration for the same
external dependency appears anywhere within the crate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within the crate before macro expansion or after macro expansion? :)

With explicit extern crates crate names are injected into modules immediately and unconditionally, and can be used in macros before waiting for them to be expanded, so it's reasonable to make this check immediately before expanding anything as well.
On the other hand, if some macro expands to an extern crate item, then you'll have a conflict instead of an override.

(The same issue exists for "items such as modules, types, or functions that conflict with the names of implicitly imported crates will cause the implicit extern crate declaration to be removed")

Copy link
Contributor

@petrochenkov petrochenkov Jul 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be possible to reuse the rules from glob imports - they also "step aside" when conflict with explicitly written names and interactions with macros were already figured out in recent name resolution RFCs.

The "extern crate declaration for the same external dependency appears anywhere within the crate" rule doesn't fit into the glob import analogy though, but I'm not sure how useful it is.

@petrochenkov
Copy link
Contributor

Can RLS/racer/Intellij jump into the toml files on "Go To Definition"? If yes, than I can live with this.
C++ IDEs always seem to struggle with anything defined by build systems.

@est31
Copy link
Member

est31 commented Jul 28, 2017

Please no. Big 👎 from me. Cargo.toml is a complicated format with many different lists of crates (dev dependencies, build dependencies, conditional dependencies, ...). For beginners looking at build.rs for example it will be far less obvious which crates they may use and which they may not. "extern crate foo;" inside integration tests plays a similar teaching role: you learn that you must use the crate's outside api and can't just use pub(crate) stuff.

Furthermore, this will make the behaviour way worse for crates named a with a [lib] name = "b" section in their Cargo.toml. Who knows that the used crate "b" corresponds to "a" in Cargo.toml? I personally think that this is a bad feature and should be deprecated, you can just use extern crate a as b;...

With the change, when I want to read a small example, I won't be able to scan the extern crate definitions at the top, I'll have to consult the Cargo.toml.

I'd very much prefer to have inferred Cargo.toml, meaning having extern crate foo = "42"; in the source code, even though its a bit more complicated to realize (and may require that we don't expand all macros because some may come from dependencies).

`Cargo.toml`, by using `extern crate foo = "0.2";` or similar. This would
require either `Cargo` or `rustc` to first scan the source before determining
the build dependencies of the existing code, a system which requires fairly
tight coupling between a build system and `rustc`, and which would almost
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think any "tight" coupling is needed for this: just add a mode to rustc which when called only scans for extern crate statements and dumps them via json. Surely the ride is a bit bumpy because deps don't exist yet so no proc macros or similar are supported, but this is not a "tight coupling". External build systems that want to support the new mode can just write a parser for that json code.

@le-jzr
Copy link

le-jzr commented Jul 28, 2017

I'd very much prefer to have inferred Cargo.toml, meaning having extern crate foo = "42"; in the source code, even though its a bit more complicated to realize (and may require that we don't expand all macros because some may come from dependencies).

This solution would be problematic when the external crate is used by multiple modules in a crate. You would either have to tag each with a version (which would complicate updating the version), or you'd need to single out one module as "the one true declaration", and have all others use it. The former is horrible, and the latter is basically equivalent to having it in Cargo.toml.

@est31
Copy link
Member

est31 commented Jul 28, 2017

This solution would be problematic when the external crate is used by multiple modules in a crate

Does doing extern crate for the same crate in multiple modules work? Interesting. If it does, I'd just say that all others should use the crate.

basically equivalent to having it in Cargo.toml.

Note that I don't think that the current solution is bad in any way, its very nice in fact. My most preferred option would be to not do anything and leave stuff as is. I just said that I prefer implied Cargo.toml to implied extern crate.

@steveklabnik
Copy link
Member

Huuuuuuge thumbs up from me; this is a big win, IMO.

@skade
Copy link
Contributor

skade commented Jul 28, 2017

I'm not a huge fan of this and my arguments go along the lines of @est31 (except wishing no inference in either way.

I'd like to add that there's plenty of scenarios where the dependency list in cargo absolutely doesn't match the one used in the lib and the binaries. Changing this would make it unclear what is used in what.

I like Rust for it's explicitness, even to the point where it is a little more verbose and I see a thrust towards becoming more and more implicit.

I'm also unsure about the interaction with other build systems.

Finally, I see how this could introduce a case where you can accidentally depend.

I don't believe it's simpler and easier, just less verbose.

@le-jzr
Copy link

le-jzr commented Jul 28, 2017

@est31 The irregularity of using extern crate in some cases, and use in others, rubs me the wrong way. extern crate is a module-level declaration with effects that are partially module-level, and partially crate-level. That is weird, and I would love getting rid of it, but I also don't think that dependencies should be imported without declaration.

IMO it would be best if extern crate is deprecated, but it's still required to use the crates in individual modules.

@skade
Copy link
Contributor

skade commented Jul 28, 2017

@le-jzr I don't have a major problem with extern crate having crate-wide effects at module level. Crates are fundamentally external and the level on which external stuff is linked in is the crate level, but the declaration directly communicates that this is the part that uses the crate.

@le-jzr
Copy link

le-jzr commented Jul 28, 2017

Actually, ignore most of what I said. After some experimenting I'm forced to conclude I had incorrect understanding of how extern crate works. '-_-

@mgattozzi
Copy link
Contributor

@skade More often then not though in Rust source code extern crate just get's lumped in main.rs and lib.rs so it's available crate wide. Personally I just have use declarations that say where I use certain crate's stuff. I see this rfc as just extending what std already does by being available everywhere in a crate.

Really my only concern about this proposal is macro imports and the possible conflicts between naming there across crates if things are implicitly imported.

@le-jzr
Copy link

le-jzr commented Jul 28, 2017

Just to be clear on what extern crate actually does...

It's entirely module level, but it actually attaches the external crate as a private sub-module of the module it's used in? Is this the correct understanding?

@cramertj
Copy link
Member Author

cramertj commented Jul 28, 2017

@mgattozzi To clarify, in macros 2.0, macro imports work just like normal items. You can use them, or you can refer to them via their full path. They aren't implicitly imported or anything.

@dpc
Copy link

dpc commented Jul 28, 2017

I am initially skeptical. I have one question. Are we currently being warned of unused extern crate? Is there going to be a way to warn us of unused crates when they are implicit, or will Cargo.toml potentially keep growing in unused dependencies?

@sfackler
Copy link
Member

@dpc there's currently a lint for unused extern crate which was recently turned form allow by default to warn by default. Not sure if it's on beta or just nightly though. I would very much like to see it updated to handle implicit crate usage as well - I've definitely had a couple of unused Cargo-level dependencies before by accident.

@gnzlbg
Copy link
Contributor

gnzlbg commented Jul 28, 2017

+1, but I'd like ways to turn this off both in Cargo.toml (that is, passing an option to rustc that disables this), and in the root of a crate #[no_implicit_imports] or similar. I would prefer if this was enabled by default, but I can understand that one might need to explicitly enable this by passing something to rustc for backwards compatibility. In that case, a Cargo.toml option (use_implicit_imports) could work, and I would like if cargo would turn it on by default when generating new projects once its stabilized.

@rpjohnst
Copy link

rpjohnst commented Jul 28, 2017

I like this idea because it matches Cargo in its convention-over-configuration approach. The vast majority of extern crate usage is just at the top of lib.rs/main.rs and matches Cargo.toml exactly, so it makes sense to carry that over while keeping the old syntax and behavior for special cases.

For reference, it also seems to go well with my proposal for minimal implicit modules.

@eira-fransham
Copy link

I really like @le-jzr's proposal, especially if you also have extern crate foo, bar, baz as syntax. You could even include it as part of the cargo new template.

@est31
Copy link
Member

est31 commented Aug 5, 2017

I want to propose an alternative to from <crate> use <path>: crate use <cratename>::<path>;

I'm a great fan of the concept of separating namespaces, and if this is done, I want to give this RFC my 👍 (but waiting for the RFC to be in its final form for this).

@petrochenkov
Copy link
Contributor

petrochenkov commented Aug 5, 2017

I've read the discussion in "Revisiting Rust’s modules, part 2" and now I agree that from <crate> use <path> is not optimal (despite being a good idea in principle) because it doesn't allow "inline" use in non-import paths. (This applies to crate use ... as well.)
Some variation of use @my_crate::path would be usable in non-import paths and therefore preferable.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Aug 7, 2017

Please don't call it implicit_dependencies---it's really "implicit imports", and I have a implicit_dependencies in #1133.

@mathstuf
Copy link

So I haven't read every comment here, but I have skimmed and searched it. I like doing this inside of my root module:

mod crates {
  pub extern crate dep1;
  pub extern crate dep2;
  pub extern crate dep3;
  pub extern crate dep4;
}

So that when crate mismatches appear, it is obvious that expected mycrate::crates::dep1::Struct; got othercrate::crates::dep1::Struct has to do with crate mismatches. When the dependencies are all imported at the top-level, seeing that there's a difference in a dependency rather than a mismatch in the crate is harder. It also makes it obvious in submodules that use crates::dep1::SomeType is using a dependency.

I don't really care about the default as long as:

  • I can disable any automatic extern crate importing in my crates; and
  • I can use extern crate inside of mod crates without having to also add an allow(explict_extern_crate) attribute.

@withoutboats withoutboats added the Ergonomics Initiative Part of the ergonomics initiative label Aug 14, 2017
@CAD97
Copy link

CAD97 commented Aug 15, 2017

@mathstuf see also #2108 which avoids some of the problems you're avoiding with that arrangement.

I would also like to make sure that we can still import crates under a module; UNIC has a lot of large data tables that it exposes. Under the understanding that most users will only need a small subset of the available tables, logical groups are each exported as a separate crate.

It is much better if, if you want to use unic_ucd_age and unic_ucd_category, you can import them as unic::ucd::age and unic::ucd::category.

It does not have to happen right now, and could be added later (and probably should go through its own RFC), but I would like to see the alias field take a path, and export the symbol at that path. So:

[dependencies]
unic_ucd_age = { version = "0.5", alias = "unic::ucd::age" }
unic_ucd_category = { version = "0.5", alias = "unic::ucd::category" }

would result in the equivalent extern crates:

mod unic {
    mod ucd {
        extern crate unic_ucd_age as age;
        extern crate unic_ucd_category as category;
    }
}

@mathstuf
Copy link

I had actually just read through that RFC yesterday. IMO, these two together would satisfy my needs (though I'm not the biggest fan of autoimport, the explicit list should be sufficient for me as well). Not sure whether I'd like it as much if either landed without the other though…

@nox
Copy link
Contributor

nox commented Aug 21, 2017

I'm strongly against this RFC, for all the reasons listed by @est31 in his various comments.

If I see use rand; in a crate, I expect that to refer in a local module rand; breaking this assumption will make reading code harder to me, because I'll have to ask myself whether rand is a crate or a module.

To me, this RFC is yet again one that optimises for writability rather than readability, I don't think that's a good thing.

@mathstuf
Copy link

@nox Note that RFC #2108 makes it so that there is a virtual crate module (basically the inverse of my mod crates pattern). Hence my wish that both land, not just one.

@nox
Copy link
Contributor

nox commented Aug 21, 2017

@mathstuf Good point, but I have more concerns about that other RFC, so I decided to comment here about my gripes with that one in isolation of the other.

@Korvox
Copy link

Korvox commented Aug 21, 2017

@nox While I don't fully like the crate:: syntax and changing the namespacing rules on modules, even in this proposal use is not ambiguous. If a use starts with anything but crate:: self:: or super:: you know its an external crate.

@est31
Copy link
Member

est31 commented Aug 23, 2017

@Korvox this specific proposal only proposes to eliminate extern crate, and put crates into the crate root next to the modules. The ambiguity fix will be as sole achievement of the modules RFC.

There has been an earlier proposal to mount automatically imported crates inside extern:: inside the root, or via @cratename or something, but the current RFC text does not reflect that.

The modules RFC is still in flux and might drop the ambiguity fix altogether or might not be adopted.

So I continue to 👎 this RFC because it introduces ambiguity issues that might not get fixed. If they get fixed, I'm neutral about this RFC and really happy about the ambiguity fix.

@rpjohnst
Copy link

The new modules RFC keeps the ambiguity fix, and it seems one of the least controversial parts of it. It also continues to cite this RFC as a major piece of itself.

@cramertj
Copy link
Member Author

Some thoughts:

extern crate *;

Multiple users have mentioned the possibility of adding some kind of opt-in or opt-out mechanism for this feature, either by extern crate *; or a Cargo.toml flag. A feature like this would make importing crates easier while still allowing users more fine-grained control when they wanted it. However, they also fracture the ecosystem and introduce two separate ways of managing external crates. i think this would make Rust harder to learn and use, as it would create a split between auto-extern crates and non-auto-extern crates. When adding a dependency, users would have to do the extra work of figuring out which system they were using, which goes against this RFC's primary goal of making the language more ergonomic.

use extern item;

Another idea was to add a use extern item; statement to pull in items from a crate. This seems confusing to me, since it's not clear how other It's unclear how this would work with multiple imports, i.e. would I have to write use extern {item1, item2}; use extern foo::{item3, item4};? I'm also unsure how this would work with ::crate::item paths. Overall, I find the RFC's current use item; syntax clearer.

use rand; intuition

@nox

If I see use rand; in a crate, I expect that to refer in a local module rand; breaking this assumption will make reading code harder to me, because I'll have to ask myself whether rand is a crate or a module.

use rand could already be referring to an external crate in today's Rust-- in order to know whether it is or not, you'd have to check the extern crate list and the Cargo.toml.

Conclusion

Personally, I feel like we've managed to resolve a great number of the initial objections to this RFC, and I'm really pleased where we've ended up. That said, there has been a great deal of discussion on this RFC and I want to make sure everyone's thoughts have been heard and carefully addressed. If you have ideas that you feel have gone unacknowledged, please speak up and I'll try my best to listen and respond thoughtfully.

@nox nox mentioned this pull request Aug 24, 2017
@petrochenkov
Copy link
Contributor

petrochenkov commented Aug 24, 2017

@cramertj

However, they also fracture the ecosystem and introduce two separate ways of managing external crates

The ecosystem will be split anyway, because extern crate x; won't go away without breakage.
No opt-out (or opt-in) is a large U+1F595 to people who never asked for this feature and would like to prohibit it in their codebases, which are in noticeable numbers, as previous discussions showed.
(I'm not arguing for extern crate *; specifically (see the next paragraphs), but for the feature being optional in general.)

Another idea was to add a use extern item; statement to pull in items from a crate. This seems confusing to me

extern::a::b::c, with extern as a path segment like self/super, answers the questions.

we've managed to resolve a great number of the initial objections to this RFC, and I'm really pleased where we've ended up.

I'm not pleased at all.
The method can't be used with crates not passes with --extern, e.g. everything from the standard distribution.
The way to enforce same behavior in root and other modules is a horrendous hack.
The crate linkage/loading question is up in the air.

The extern::my_crate::a/[my_crate]::a/@my_crate::a (even plain use my_crate::a from #2108, if it were backward compatible) avoid these problems, and I think it's a superior solution.

  • First it keeps the "source code asks, crate loader provides" model that is currently in action. You unambiguously mention a crate name somehow (extern crate my_crate;/@my_crate::a/etc), then the crate is searched in all appropriate locations - --extern options, library search directories (including standard library and compiler directories), etc. --extern options are passive, they can be written without any effect unless source code is looking for them. Thus we avoid the issue with excessive crate linking/loading.
  • [my_crate]::a-style paths can be used uniformly in any modules including root, in all positions including non-import ones, without hacks.
  • Using or not using [my_crate]::a-style paths is a choice, not something that happens implicitly and can't be turned off.

I find the RFC's current use item; syntax clearer.

I think there is a possibility to keep the general model of extern::my_crate::a, but use syntax my_crate::a::b without extra symbols (in use and non-use paths equally) through "extern crate fallback".
If the first segment in a path a::b::c is unresolved we can try to search it as an extern crate (i.e. first in --extern options, then library directories, as usual).
This shouldn't affect performance in theory, the unneeded crate search overhead will happen only when the code is guaranteed to fail compilation with resolution error.

The two primary issues are:

  • I'm not sure how to integrate this fallback into the import resolution algorithm, e.g. what priorities it will have compared to globs and unexpanded macros. This needs experimentation.
  • The second issue is a more philosophical one - we "link" to an external crate without any visual clue, like pull it out of nowhere. This RFC suffers from this issue as well.
    Personally, I don't think I like it, I'd prefer some visual signal.

(For reference, a bikeshedding thread with a poll for specific syntax - https://internals.rust-lang.org/t/poll-which-other-crate-relative-path-syntax-do-you-prefer/5744.)

@spernsteiner
Copy link

I'd like to see a clearer statement in the RFC text regarding the future of extern crate. Is this RFC proposing the new --extern-based importing as an eventual replacement for extern crate, or are the two mechanisms meant to exist side-by-side for the foreseeable future? The claim that extern crate is supported "for backwards compatibility, with the option of phasing it out in future Rust epochs" suggests that replacement is the goal, but there's no explicit plan to deprecate extern crate that I can see.

@SimonSapin
Copy link
Contributor

SimonSapin commented Aug 24, 2017

with the option of phasing it out in future Rust epochs.

I think this part of the RFC should be removed. First, it is not clear what “phasing out” is intended to mean. An “unnecessary extern crate” warning is fine, but the mention of epochs suggests making it a hard error. I fail to see any benefit in doing that. It’s not gonna remove any compiler maintenance burden, since the compiler needs to keep supporting older epochs/checkpoints.

In other words, I’m disagreeing with @aturon’s comment at #2088 (comment).

In general, just because we can turn a warning into a hard error in a new epoch/checkpoint doesn’t mean we should. I think “dislike of old things” shouldn’t be a sufficient reason.


Edit: I should mention that, other than this detail, this RFC looks good and I’m if favor of doing this. 👍

@cramertj
Copy link
Member Author

@petrochenkov

extern::a::b::c, with extern as a path segment

I'd personally be okay with something along those lines. This seems highly related to module-system proposals to add a crate:: prefix to paths. use extern::rand::random; seems relatively straightforward. Presumably, this would allow all extern crate foo; declarations to be replaced by use extern::foo;. This would also simplify the "is this crate used?" question the RFC introduces-- it removes full name resolution, replacing it with a simple search for use extern::<some_crate>; or extern::<some_crate> paths. This would also remove the need for inferring the crate list from the --extern flag.

The one downside is that it makes paths to external items more verbose than under the current proposal. It's still an improvement on the current system, though, since one can just add use extern::foo; if they want to avoid having to write many extern::foo::bar paths.

@petrochenkov
Copy link
Contributor

@cramertj

The one downside is that it makes paths to external items more verbose than under the current proposal. It's still an improvement on the current system, though, since one can just add use extern::foo; if they want to avoid having to write many extern::foo::bar paths.

#1400 can help with this as well (IIRC, it even was mentioned in some earlier "module reform" proposal).

@retep998
Copy link
Member

I'd definitely be less opposed to this RFC if all the implicit crates were put under a special namespace, and an implicit crate was only considered to be pulled in if and only if you referred to a symbol from the implicit crate.

Also yes please to #1400, that's such a basic quality of life ergonomic improvement that I'm amazed we're wasting so much time on other things instead of pushing forward on nested use paths.

@rpjohnst
Copy link

If we're going to add a prefix to paths, I definitely prefer adding something like crate:: to local paths, over something like extern::. This feels more consistent with other languages' behavior, and more internally consistent (all absolute paths start with a reference to a crate).

@petrochenkov
Copy link
Contributor

petrochenkov commented Aug 25, 2017

@rpjohnst
There's difference.
With prefix-less extern paths, when you have a non-use path

let x = a::b::c;

you still don't know if a is an extern crate or just some name in scope.
So you either have to further diverge use and non-use paths, or use the "extern crate fallback" without any visual clue.
(That's without backward compatibility issues.)

EDIT: Oh wait, non-use path from other crates are supposed to be written as ::my_crate::a::b?
Then that's okay, only backward compatibility issues remain.


This feels more consistent with other languages' behavior

If "other languages" is C++ (which I think should be prioritized when deciding on syntactic compatibilities), then Rust is already consistent - both use ::a::b::c for absolute paths.

@rpjohnst
Copy link

rpjohnst commented Aug 25, 2017

That's not the comparison I was making. My point is that in C++, C#, Java, etc. absolute paths (whether they begin with :: or not) all start on equivalent footing w.r.t external and internal paths. They don't start with extern:: for dependencies and nothing for local paths- they start with the top-level namespace of the "crate" they refer to, and that applies to the local one just as much as external ones.

In those languages, however, you don't usually wind up with both a library and a binary with the same "crate" name, while in Rust you often do. So, instead of using the_crate::path::to::item for both external and internal paths, internal paths replace the local crate name with crate::. Beyond fixing the library/binary ambiguity, this makes renaming crates easier.

Putting :: at the start of all absolute paths is another question, but it's fine with me, since that's what we already do.

@cramertj
Copy link
Member Author

Thanks everyone for the great discussion! I've learned a lot from the conversation here, and it's helped me to identify several of the key flaws in the original proposal. The reliance upon the --extern flag means that this proposal couldn't ever completely replicate the functionality of extern crate. This RFC would also make it harder to tell which paths referred to local modules vs. external crates.

In light of the lessons learned here, I'm closing this RFC in favor of @aturon's new paths and visibility RFC. This RFC is the latest and last iteration of the modules proposals. It takes a much more conservative, straightforward approach to cleaning up paths and visibility in Rust.

Under the new proposal, extern crate would be deprecated in the next epoch. Paths that reference the local crate will begin with crate:: or local::. All other paths (those that don't begin with crate:: or local::) reference external crates. This allows us to concretely determine the list of crates that need to be pulled in without needing to look at the --extern flag or any other rustc/Cargo interaction.

Please take a look at the new proposal and leave your feedback there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ergonomics Initiative Part of the ergonomics initiative T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.