Skip to content

Instantly share code, notes, and snippets.

@mitsuhiko
Created April 3, 2017 08:12
Show Gist options
  • Save mitsuhiko/9e656f8cd9d9d77160430ce04a1744f3 to your computer and use it in GitHub Desktop.
Save mitsuhiko/9e656f8cd9d9d77160430ce04a1744f3 to your computer and use it in GitHub Desktop.
  • Feature Name: public_private_dependencies
  • Start Date: 2017-04-03
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

Introduce a public/private distinction to crate dependencies.

Motivation

The crates ecosystem has greatly expanded since Rust 1.0 and with that a few patterns for dependencies have evolved that challenge the currently existing dependency declaration system in cargo and rust. The most common problem is that a crate A depends on another crate B but some of the types from crate B are exposed through the API in crate A. This causes problems in practice if that dependency B is also used by the user's code itself which often leaves users in less than ideal situations where either their code refuses to compile because different versions of those libraries are requested or where compiler messages are less than clear.

The introduction of an explicit distinction between public and private dependencies can solve some of these issues and also let us lift some restrictions that should make some code compile that previously was prevented from compiling by restrictions in cargo.

Q: What is a public dependency?
A: a dependency is public if some of the types or trait of that dependency is itself exported through the main crate. The most common places where this happens is obviously return values and function parameter but obviously the same applies to trait implementations and many other things. Because public can be tricky to determine for a user this RFC proposes to extend the compiler infrastructure to detect the concept of "public dependency". This will help the user understanding this concept and avoid making mistakes in the Cargo.toml

Effectively the idea is that if your own library bumps a public dependency it means that it's a breaking change of your own crate.

Q: What is a private dependency?
A: On the other hand a private dependency is contained within your crate and effectively invisible for users of your crate. As a result private dependencies can be freely duplicated. This distinction will also make it possible to relax some restrictions that currently exist in Cargo which sometimes prevent crates from compiling.

Q: Can public become private later?
A: Public dependencies are public within a reachable subgraph but can become private if a crate stops exposing a public dependency. For instance it is very possible to have a family of crates that all depend on a utility crate that provides common types which is a public dependency for all of them. However your own crate only becomes a user of this utility crate through another dependency that itself does not expose any of the types from that utility crate and as such the dependency is marked private.

Q: Where is public / private defined?
Dependencies are private by default and are made public through a public flag in the dependency in the Cargo.toml file. This also means that crates created before the implementation of this RFC will have all their dependencies private.

Q: How is backwards compatibility handled?
A: It will continue to be permissible to "leak" dependencies and there are even some use cases of this, however the compiler or cargo will emit warnings if private dependencies become part of the public API. Later it might even become invalid to publish new crates without explicitly silencing these warnings or marking the dependencies as public.

Q: Can I export a type from a private dependency as my own?
For now it will not be strictly permissible to privately depend on a crate and export a type from their as your own. The reason for this is that at the moment it is not possible to force this type to be distinct. This means that users of the crate might accidentally start depending on that type to be compatible if the user starts to depend on the crate that actually implements that type.

Detailed design

There are a few areas that require to be changed for this RFC:

  • The compiler needs to be extended to understand when crate dependencies are considered a public dependency
  • The Cargo.toml manifest needs to be extended to support declaring public dependencies
  • The cargo publish process needs to be changed to warn (or prevent) the publishing of crates that have undeclared public dependencies
  • crates.io should show public dependencies more prominently than private ones.

Compiler Changes

XXX: add me

In some situations it can be necessary to allow private dependencies to become part of the public API. In that case one can permit this with #[allow(external_private_dependency)]. This is particularly useful when paired with #[doc(hidden)] and other already existing hacks.

Changes to Cargo.toml

The Cargo.toml file will be amended to support the new public parameter on dependencies. Old cargo versions will emit a warning when this key is encountered but otherwise continue. Since the default for a dependency to be private only public ones will need to be tagged which should be the minority.

Example dependency:

[dependencies]
url = { version = "1.4.0", public = true }

Changes to Cargo Publishing

When a new crate version is published Cargo will warn about types and traits that the compiler determined to be public but did not come from a public dependency. For now it should be possible to publish anyways but in some period in the future it will be necessary to explicitly mark all public dependencies as such or explicitly mark them with #[allow(external_private_dependency)].

How We Teach This

What names and terminology work best for these concepts and why? How is this idea best presented—as a continuation of existing Rust patterns, or as a wholly new one?

Would the acceptance of this proposal change how Rust is taught to new users at any level? How should this feature be introduced and taught to existing Rust users?

What additions or changes to the Rust Reference, The Rust Programming Language, and/or Rust by Example does it entail?

Drawbacks

Why should we not do this?

Alternatives

What other designs have been considered? What is the impact of not doing this?

Unresolved questions

What parts of the design are still TBD?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment