From fd6a84aa83e6ce4431263a361bb52df12ddaa9dc Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 10 Aug 2020 09:51:22 -0700 Subject: [PATCH 1/4] Add chapters on dependency resolution and SemVer compatibility. --- .github/workflows/main.yml | 1 + src/doc/README.md | 6 + src/doc/semver-check/Cargo.toml | 10 + src/doc/semver-check/src/main.rs | 217 +++++ src/doc/src/SUMMARY.md | 2 + src/doc/src/reference/index.md | 2 + src/doc/src/reference/manifest.md | 8 + src/doc/src/reference/resolver.md | 415 +++++++++ src/doc/src/reference/semver.md | 1321 +++++++++++++++++++++++++++++ 9 files changed, 1982 insertions(+) create mode 100644 src/doc/semver-check/Cargo.toml create mode 100644 src/doc/semver-check/src/main.rs create mode 100644 src/doc/src/reference/resolver.md create mode 100644 src/doc/src/reference/semver.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69ab5b41caf..6b762187b4c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,6 +92,7 @@ jobs: - run: rustup update nightly && rustup default nightly - run: rustup component add rust-docs - run: ci/validate-man.sh + - run: cd src/doc/semver-check && cargo run - run: | mkdir mdbook curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.3.7/mdbook-v0.3.7-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook diff --git a/src/doc/README.md b/src/doc/README.md index 92bfd8a0580..79181b7f699 100644 --- a/src/doc/README.md +++ b/src/doc/README.md @@ -56,6 +56,12 @@ To rebuild the man pages, run the script `build-man.sh` in the `src/doc` directo $ ./build-man.sh ``` +### SemVer chapter tests + +There is a script to verify that the examples in the SemVer chapter work as +intended. To run the tests, go into the `semver-check` directory and run +`cargo run`. + ## Contributing We'd love your help with improving the documentation! Please feel free to diff --git a/src/doc/semver-check/Cargo.toml b/src/doc/semver-check/Cargo.toml new file mode 100644 index 00000000000..bf77dfabb6c --- /dev/null +++ b/src/doc/semver-check/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "semver-check" +version = "0.1.0" +authors = ["Eric Huss"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tempfile = "3.1.0" diff --git a/src/doc/semver-check/src/main.rs b/src/doc/semver-check/src/main.rs new file mode 100644 index 00000000000..ab4071d7073 --- /dev/null +++ b/src/doc/semver-check/src/main.rs @@ -0,0 +1,217 @@ +//! Test runner for the semver compatibility doc chapter. +//! +//! This extracts all the "rust" annotated code blocks and tests that they +//! either fail or succeed as expected. This also checks that the examples are +//! formatted correctly. +//! +//! An example with the word "MINOR" at the top is expected to successfully +//! build against the before and after. Otherwise it should fail. A comment of +//! "// Error:" will check that the given message appears in the error output. + +use std::error::Error; +use std::fs; +use std::path::Path; +use std::process::Command; + +fn main() { + if let Err(e) = doit() { + println!("error: {}", e); + std::process::exit(1); + } +} + +const SEPARATOR: &str = "///////////////////////////////////////////////////////////"; + +fn doit() -> Result<(), Box> { + let filename = std::env::args() + .skip(1) + .next() + .unwrap_or_else(|| "../src/reference/semver.md".to_string()); + let contents = fs::read_to_string(filename)?; + let mut lines = contents.lines().enumerate(); + + loop { + // Find a rust block. + let block_start = loop { + match lines.next() { + Some((lineno, line)) => { + if line.trim().starts_with("```rust") && !line.contains("skip") { + break lineno + 1; + } + } + None => return Ok(()), + } + }; + // Read in the code block. + let mut block = Vec::new(); + loop { + match lines.next() { + Some((_, line)) => { + if line.trim() == "```" { + break; + } + block.push(line); + } + None => { + return Err(format!( + "rust block did not end for example starting on line {}", + block_start + ) + .into()); + } + } + } + // Split it into the separate source files. + let parts: Vec<_> = block.split(|line| line.trim() == SEPARATOR).collect(); + if parts.len() != 4 { + return Err(format!( + "expected 4 sections in example starting on line {}, got {}:\n{:?}", + block_start, + parts.len(), + parts + ) + .into()); + } + let join = |part: &[&str]| { + let mut result = String::new(); + result.push_str("#![allow(unused)]\n#![deny(warnings)]\n"); + result.push_str(&part.join("\n")); + if !result.ends_with('\n') { + result.push('\n'); + } + result + }; + let expect_success = parts[0][0].contains("MINOR"); + println!("Running test from line {}", block_start); + if let Err(e) = run_test( + join(parts[1]), + join(parts[2]), + join(parts[3]), + expect_success, + ) { + return Err(format!( + "test failed for example starting on line {}: {}", + block_start, e + ) + .into()); + } + } +} + +const CRATE_NAME: &str = "updated_crate"; + +fn run_test( + before: String, + after: String, + example: String, + expect_success: bool, +) -> Result<(), Box> { + let tempdir = tempfile::TempDir::new()?; + let before_p = tempdir.path().join("before.rs"); + let after_p = tempdir.path().join("after.rs"); + let example_p = tempdir.path().join("example.rs"); + compile(before, &before_p, CRATE_NAME, false, true)?; + compile(example.clone(), &example_p, "example", true, true)?; + compile(after, &after_p, CRATE_NAME, false, true)?; + compile(example, &example_p, "example", true, expect_success)?; + Ok(()) +} + +fn check_formatting(path: &Path) -> Result<(), Box> { + match Command::new("rustfmt") + .args(&["--edition=2018", "--check"]) + .arg(path) + .status() + { + Ok(status) => { + if !status.success() { + return Err(format!("failed to run rustfmt: {}", status).into()); + } + return Ok(()); + } + Err(e) => { + return Err(format!("failed to run rustfmt: {}", e).into()); + } + } +} + +fn compile( + mut contents: String, + path: &Path, + crate_name: &str, + extern_path: bool, + expect_success: bool, +) -> Result<(), Box> { + // If the example has an error message, remove it so that it can be + // compared with the actual output, and also to avoid issues with rustfmt + // moving it around. + let expected_error = match contents.find("// Error:") { + Some(index) => { + let start = contents[..index].rfind(|ch| ch != ' ').unwrap(); + let end = contents[index..].find('\n').unwrap(); + let error = contents[index + 9..index + end].trim().to_string(); + contents.replace_range(start + 1..index + end, ""); + Some(error) + } + None => None, + }; + let crate_type = if contents.contains("fn main()") { + "bin" + } else { + "rlib" + }; + + fs::write(path, &contents)?; + check_formatting(path)?; + let out_dir = path.parent().unwrap(); + let mut cmd = Command::new("rustc"); + cmd.args(&[ + "--edition=2018", + "--crate-type", + crate_type, + "--crate-name", + crate_name, + "--out-dir", + ]); + cmd.arg(&out_dir); + if extern_path { + let epath = out_dir.join(format!("lib{}.rlib", CRATE_NAME)); + cmd.arg("--extern") + .arg(format!("{}={}", CRATE_NAME, epath.display())); + } + cmd.arg(path); + let output = cmd.output()?; + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + match (output.status.success(), expect_success) { + (true, true) => Ok(()), + (true, false) => Err(format!( + "expected failure, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n", + path.display(), + contents, + stderr + ) + .into()), + (false, true) => Err(format!( + "expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n", + path.display(), + contents, + stderr + ) + .into()), + (false, false) => { + if expected_error.is_none() { + return Err("failing test should have an \"// Error:\" annotation ".into()); + } + let expected_error = expected_error.unwrap(); + if !stderr.contains(&expected_error) { + Err(format!( + "expected error message not found in compiler output\nExpected: {}\nGot:\n{}\n", + expected_error, stderr + ) + .into()) + } else { + Ok(()) + } + } + } +} diff --git a/src/doc/src/SUMMARY.md b/src/doc/src/SUMMARY.md index e462b8ec357..daba6d2f06e 100644 --- a/src/doc/src/SUMMARY.md +++ b/src/doc/src/SUMMARY.md @@ -35,6 +35,8 @@ * [Source Replacement](reference/source-replacement.md) * [External Tools](reference/external-tools.md) * [Registries](reference/registries.md) + * [Dependency Resolution](reference/resolver.md) + * [SemVer Compatibility](reference/semver.md) * [Unstable Features](reference/unstable.md) * [Cargo Commands](commands/index.md) diff --git a/src/doc/src/reference/index.md b/src/doc/src/reference/index.md index dd41258fd73..1d172beba4c 100644 --- a/src/doc/src/reference/index.md +++ b/src/doc/src/reference/index.md @@ -18,4 +18,6 @@ The reference covers the details of various areas of Cargo. * [Source Replacement](source-replacement.md) * [External Tools](external-tools.md) * [Registries](registries.md) +* [Dependency Resolution](resolver.md) +* [SemVer Compatibility](semver.md) * [Unstable Features](unstable.md) diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index 853ffc3c273..dd5efa1f5d2 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -95,6 +95,14 @@ Versioning](https://semver.org/), so make sure you follow some basic rules: traits, fields, types, functions, methods or anything else. * Use version numbers with three numeric parts such as 1.0.0 rather than 1.0. +See the [Resolver] chapter for more information on how Cargo uses versions to +resolve dependencies, and for guidelines on setting your own version. See the +[Semver compatibility] chapter for more details on exactly what constitutes a +breaking change. + +[Resolver]: resolver.md +[Semver compatibility]: semver.md + #### The `authors` field diff --git a/src/doc/src/reference/resolver.md b/src/doc/src/reference/resolver.md new file mode 100644 index 00000000000..ba82659afa9 --- /dev/null +++ b/src/doc/src/reference/resolver.md @@ -0,0 +1,415 @@ +# Dependency Resolution + +One of Cargo's primary tasks is to determine the versions of dependencies to +use based on the version requirements specified in each package. This process +is called "dependency resolution" and is performed by the "resolver". The +result of the resolution is stored in the `Cargo.lock` file which "locks" the +dependencies to specific versions, and keeps them fixed over time. + +The resolver attempts to unify common dependencies while considering possibly +conflicting requirements. The sections below provide some details on how these +constraints are handled, and how to work with the resolver. + +See the chapter [Specifying Dependencies] for more details about how +dependency requirements are specified. + +The [`cargo tree`] command can be used to visualize the result of the +resolver. + +[Specifying Dependencies]: specifying-dependencies.md +[`cargo tree`]: ../commands/cargo-tree.md + +## SemVer compatibility + +Cargo uses [SemVer] for specifying version numbers. This establishes a common +convention for what is compatible between different versions of a package. See +the [SemVer Compatibility] chapter for guidance on what is considered a +"compatible" change. This notion of "compatibility" is important because Cargo +assumes it should be safe to update a dependency within a compatibility range +without breaking the build. + +Versions are considered compatible if their left-most non-zero +major/minor/patch component is the same. For example, `1.0.3` and `1.1.0` are +considered compatible, and thus it should be safe to update from the older +release to the newer one. However, an update from `1.1.0` to `2.0.0` would not +be allowed to be made automatically. This convention also applies to versions +with leading zeros. For example, `0.1.0` and `0.1.2` are compatible, but +`0.1.0` and `0.2.0` are not. Similarly, `0.0.1` and `0.0.2` are not +compatible. + +As a quick refresher, the *version requirement* syntax Cargo uses for +dependencies is: + +Requirement | Example | Equivalence | Description +--|--------|--|------------- +Caret | `1.2.3` or `^1.2.3` | >=1.2.3, <2.0.0 | Any SemVer-compatible version of at least the given value. +Tilde | `~1.2` | >=1.2.0, <1.3.0 | Minimum version, with restricted compatibility range. +Wildcard | `1.*` | >=1.0.0, <2.0.0 | Any version in the `*` position. +Equals | `=1.2.3` | =1.2.3 | Exactly the specified version only. +Comparison | `>1.1` | >=1.2.0 | Naive numeric comparison of specified digits. +Compound | >=1.2, <1.5 | >1.2.0, <1.5.0 | Multiple requirements that must be simultaneously satisfied. + +When multiple packages specify a dependency for a common package, the resolver +attempts to ensure that they use the same version of that common package, as +long as they are within a SemVer compatibility range. It also attempts to use +the greatest version currently available within that compatibility range. For +example, if there are two packages in the resolve graph with the following +requirements: + +```toml +# Package A +[dependencies] +bitflags = "1.0" + +# Package B +[dependencies] +bitflags = "1.1" +``` + +If at the time the `Cargo.lock` file is generated, the greatest version of +`bitflags` is `1.2.1`, then both packages will use `1.2.1` because it is the +greatest within the compatibility range. If `2.0.0` is published, it will +still use `1.2.1` because `2.0.0` is considered incompatible. + +If multiple packages have a common dependency with semver-incompatible +versions, then Cargo will allow this, but will build two separate copies of +the dependency. For example: + +```toml +# Package A +[dependencies] +rand = "0.7" + +# Package B +[dependencies] +rand = "0.6" +``` + +The above will result in Package A using the greatest `0.7` release (`0.7.3` +at the time of this writing) and Package B will use the greatest `0.6` release +(`0.6.5` for example). This can lead to potential problems, see the +[Version-incompatibility hazards] section for more details. + +Multiple versions within the same compatibility range are not allowed and will +result in a resolver error if it is constrained to two different versions +within a compatibility range. For example, if there are two packages in the +resolve graph with the following requirements: + +```toml +# Package A +[dependencies] +log = "=0.4.11" + +# Package B +[dependencies] +log = "=0.4.8" +``` + +The above will fail because it is not allowed to have two separate copies of +the `0.4` release of the `log` package. + +[SemVer]: https://semver.org/ +[SemVer Compatibility]: semver.md +[Version-incompatibility hazards]: #version-incompatibility-hazards + +### Version-incompatibility hazards + +When multiple versions of a crate appear in the resolve graph, this can cause +problems when types from those crates are exposed by the crates using them. +This is because the types and items are considered different by the Rust +compiler, even if they have the same name. Libraries should take care when +publishing a SemVer-incompatible version (for example, publishing `2.0.0` +after `1.0.0` has been in use), particularly for libraries that are widely +used. + +The "[semver trick]" is a workaround for this problem of publishing a breaking +change while retaining compatibility with older versions. The linked page goes +into detail about what the problem is and how to address it. In short, when a +library wants to publish a SemVer-breaking release, publish the new release, +and also publish a point release of the previous version that reexports the +types from the newer version. + +These incompatibilities usually manifest as a compile-time error, but +sometimes they will only appear as a runtime misbehavior. For example, let's +say there is a common library named `foo` that ends up appearing with both +version `1.0.0` and `2.0.0` in the resolve graph. If [`downcast_ref`] is used +on a object created by a library using version `1.0.0`, and the code calling +`downcast_ref` is downcasting to a type from version `2.0.0`, the downcast +will fail at runtime. + +It is important to make sure that if you have multiple versions of a library +that you are properly using them, especially if it is ever possible for the +types from different versions to be used together. The [`cargo tree +-d`][`cargo tree`] command can be used to identify duplicate versions and +where they come from. Similarly, it is important to consider the impact on the +ecosystem if you publish a SemVer-incompatible version of a popular library. + +[semver trick]: https://github.com/dtolnay/semver-trick +[`downcast_ref`]: ../../std/any/trait.Any.html#method.downcast_ref + +### Pre-releases + +SemVer has the concept of "pre-releases" with a dash in the version, such as +`1.0.0-alpha`, or `1.0.0-beta`. Cargo will avoid automatically using +pre-releases unless explicitly asked. For example, if `1.0.0-alpha` of package +`foo` is published, then a requirement of `foo = "1.0"` will *not* match, and +will return an error. The pre-release must be specified, such as `foo = +"1.0.0-alpha"`. Similarly [`cargo install`] will avoid pre-releases unless +explicitly asked to install one. + +Cargo allows "newer" pre-releases to be used automatically. For example, if +`1.0.0-beta` is published, then a requirement `foo = "1.0.0-alpha"` will allow +updating to the `beta` version. Beware that pre-release versions can be +unstable, and as such care should be taken when using them. Some projects may +choose to publish breaking changes between pre-release versions. It is +recommended to not use pre-release dependencies in a library if your library +is not also a pre-release. Care should also be taken when updating your +`Cargo.lock`, and be prepared if a pre-release update causes issues. + +The pre-release tag may be separated with periods to distinguish separate +components. Numeric components will use numeric comparison. For example, +`1.0.0-alpha.4` will use numeric comparison for the `4` component. That means +that if `1.0.0-alpha.11` is published, that will be chosen as the greatest +release. Non-numeric components are compared lexicographically. + +[`cargo install`]: ../commands/cargo-install.md + +### Version metadata + +SemVer has the concept of "version metadata" with a plus in the version, such +as `1.0.0+21AF26D3`. This metadata is usually ignored, and should not be used +in a version requirement. You should never publish multiple versions that +differ only in the metadata tag (note, this is a [known issue] with +[crates.io] that currently permits this). + +[known issue]: https://github.com/rust-lang/crates.io/issues/1059 +[crates.io]: https://crates.io/ + +## Other constraints + +Version requirements aren't the only constraint that the resolver considers +when selecting and unifying dependencies. The following sections cover some of +the other constraints that can affect resolution. + +### Features + +The resolver resolves the graph as-if the [features] of all [workspace] +members are enabled. This ensures that any optional dependencies are available +and properly resolved with the rest of the graph when features are added or +removed with the `--features` command-line flag. The actual features used when +*compiling* a crate will depend on the features enabled on the command-line. + +Dependencies are resolved with the union of all features enabled on them. For +example, if one package depends on the [`im`] package with the [`serde` +dependency] enabled and another package depends on it with the [`rayon` +dependency] enabled, then `im` will be built with both features enabled, and +the `serde` and `rayon` crates will be included in the resolve graph. If no +packages depend on `im` with those features, then those optional dependencies +will be ignored, and they will not affect resolution. + +The resolver will skip over versions of packages that are missing required +features. For example, if a package depends on version `^1` of [`regex`] with +the [`perf` feature], then the oldest version it can select is `1.3.0`, +because versions prior to that did not contain the `perf` feature. Similarly, +if a feature is removed from a new release, then packages that require that +feature will be stuck on the older releases that contain that feature. It is +discouraged to remove features in a SemVer-compatible release. + +[`im`]: https://crates.io/crates/im +[`perf` feature]: https://github.com/rust-lang/regex/blob/1.3.0/Cargo.toml#L56 +[`rayon` dependency]: https://github.com/bodil/im-rs/blob/v15.0.0/Cargo.toml#L47 +[`regex`]: https://crates.io/crates/regex +[`serde` dependency]: https://github.com/bodil/im-rs/blob/v15.0.0/Cargo.toml#L46 +[features]: features.md +[workspace]: workspaces.md + +### `links` + +The [`links` field] is used to ensure only one copy of a native library is +linked into a binary. It is an error if multiple SemVer-incompatible versions +of a package with a `links` field appears in the resolve graph. For example, +it is an error if one package depends on [`libgit2-sys`] version `0.11` and +another depends on `0.12`, because Cargo is unable to unify those, but they +both link to the `git2` native library. Due to this rigid requirement, it is +encouraged to be very careful when making SemVer-incompatible releases with +the `links` field if your library is in common use. + +[`links` field]: manifest.md#the-links-field +[`libgit2-sys`]: https://crates.io/crates/libgit2-sys + +### Yanked versions + +[Yanked releases][yank] are those that are marked that they should not be +used. When the resolver is building the graph, it will ignore all yanked +releases unless they already exist in the `Cargo.lock` file. + +[yank]: publishing.md#cargo-yank + +## Dependency updates + +Dependency resolution is automatically performed by all Cargo commands that +need to know about the dependency graph. For example, [`cargo build`] will run +the resolver to discover all the dependencies to build. After the first time +it runs, the result is stored in the `Cargo.lock` file. Subsequent commands +will run the resolver, keeping dependencies locked to the versions in +`Cargo.lock` *if it can*. + +If the dependency list in `Cargo.toml` has been modified, for example changing +the version of a dependency from `1.0` to `2.0`, then the resolver will select +a new version for that dependency that matches the new requirements. If that +new dependency introduces new requirements, those new requirements may also +trigger additional updates. The `Cargo.lock` file will be updated with the new +result. The `--locked` or `--frozen` flags can be used to change this behavior +to prevent automatic updates when requirements change, and return an error +instead. + +[`cargo update`] can be used to update the entries in `Cargo.lock` when new +versions are published. Without any options, it will attempt to update all +packages in the lock file. The `-p` flag can be used to target the update for +a specific package, and other flags such as `--aggressive` or `--precise` can +be used to control how versions are selected. + +[`cargo build`]: ../commands/cargo-build.md +[`cargo update`]: ../commands/cargo-update.md + +## Overrides + +Cargo has several mechanisms to override dependencies within the graph. The +[Overriding Dependencies] chapter goes into detail on how to use overrides. +The overrides appear as an overlay to a registry, replacing the patched +version with the new entry. Otherwise, resolution is performed like normal. + +[Overriding Dependencies]: overriding-dependencies.md + +## Dependency kinds + +There are three kinds of dependencies in a package: normal, [build], and +[dev][dev-dependencies]. For the most part these are all treated the same from +the perspective of the resolver. One difference is that dev-dependencies for +non-workspace members are always ignored, and do not influence resolution. + +[Platform-specific dependencies] with the `[target]` table are resolved as-if +all platforms are enabled. In other words, the resolver ignores the platform +or `cfg` expression. + +[build]: specifying-dependencies.md#build-dependencies +[dev-dependencies]: specifying-dependencies.md#development-dependencies +[Platform-specific dependencies]: specifying-dependencies.md#platform-specific-dependencies + +### dev-dependency cycles + +Usually the resolver does not allow cycles in the graph, but it does allow +them for [dev-dependencies]. For example, project "foo" has a dev-dependency +on "bar", which has a normal dependency on "foo" (usually as a "path" +dependency). This is allowed because there isn't really a cycle from the +perspective of the build artifacts. In this example, the "foo" library is +built (which does not need "bar" because "bar" is only used for tests), and +then "bar" can be built depending on "foo", then the "foo" tests can be built +linking to "bar". + +Beware that this can lead to confusing errors. In the case of building library +unit tests, there are actually two copies of the library linked into the final +test binary: the one that was linked with "bar", and the one built that +contains the unit tests. Similar to the issues highlighted in the +[Version-incompatibility hazards] section, the types between the two are not +compatible. Be careful when exposing types of "foo" from "bar" in this +situation, since the "foo" unit tests won't treat them the same as the local +types. + +If possible, try to split your package into multiple packages and restructure +it so that it remains strictly acyclic. + +## Recommendations + +The following are some recommendations for setting the version within your +package, and for specifying dependency requirements. These are general +guidelines that should apply to common situations, but of course some +situations may require specifying unusual requirements. + +* Follow the [SemVer guidelines] when deciding how to update your version + number, and whether or not you will need to make a SemVer-incompatible + version change. +* Use caret requirements for dependencies, such as `"1.2.3"`, for most + situations. This ensures that the resolver can be maximally flexible in + choosing a version while maintaining build compatibility. + * Specify all three components with the version you are currently using. + This helps set the minimum version that will be used, and ensures that + other users won't end up with an older version of the dependency that + might be missing something that your package requires. + * Avoid `*` requirements, as they are not allowed on [crates.io], and they + can pull in SemVer-breaking changes during a normal `cargo update`. + * Avoid overly broad version requirements. For example, `>=2.0.0` can pull + in any SemVer-incompatible version, like version `5.0.0`, which can result + in broken builds in the future. + * Avoid overly narrow version requirements if possible. For example, if you + specify a tilde requirement like `bar="~1.3"`, and another package + specifies a requirement of `bar="1.4"`, this will fail to resolve, even + though minor releases should be compatible. +* Try to keep the dependency versions up-to-date with the actual minimum + versions that your library requires. For example, if you have a requirement + of `bar="1.0.12"`, and then in a future release you start using new features + added in the `1.1.0` release of "bar", update your dependency requirement to + `bar="1.1.0"`. + + If you fail to do this, it may not be immediately obvious because Cargo can + opportunistically choose the newest version when you run a blanket `cargo + update`. However, if another user depends on your library, and runs `cargo + update -p your-library`, it will *not* automatically update "bar" if it is + locked in their `Cargo.lock`. It will only update "bar" in that situation if + the dependency declaration is also updated. Failure to do so can cause + confusing build errors for the user using `cargo update -p`. +* If two packages are tightly coupled, then an `=` dependency requirement may + help ensure that they stay in sync. For example, a library with a companion + proc-macro library will sometimes make assumptions between the two libraries + that won't work well if the two are out of sync (and it is never expected to + use the two libraries independently). The parent library can use an `=` + requirement on the proc-macro, and re-export the macros for easy access. +* `0.0.x` versions can be used for packages that are permanently unstable. + +In general, the stricter you make the dependency requirements, the more likely +it will be for the resolver to fail. Conversely, if you use requirements that +are too loose, it may be possible for new versions to be published that will +break the build. + +[SemVer guidelines]: semver.md + +## Troubleshooting + +The following illustrates some problems you may experience, and some possible +solutions. + +### SemVer-breaking patch release breaks the build + +Sometimes a project may inadvertently publish a point release with a +SemVer-breaking change. When users update with `cargo update`, they will pick +up this new release, and then their build may break. In this situation, it is +recommended that the project should [yank] the release, and either remove the +SemVer-breaking change, or publish it as a new SemVer-major version increase. + +If the change happened in a third-party project, if possible try to +(politely!) work with the project to resolve the issue. + +While waiting for the release to be yanked, some workarounds depend on the +circumstances: + +* If your project is the end product (such as a binary executable), just avoid + updating the offending package in `Cargo.lock`. This can be done with the + `--precise` flag in [`cargo update`]. +* If you publish a binary on [crates.io], then you can temporarily add an `=` + requirement to force the dependency to a specific good version. + * Binary projects can alternatively recommend users to use the `--locked` + flag with [`cargo install`] to use the original `Cargo.lock` that contains + the known good version. +* Libraries may also consider publishing a temporary new release with stricter + requirements that avoid the troublesome dependency. You may want to consider + using range requirements (instead of `=`) to avoid overly-strict + requirements that may conflict with other packages using the same + dependency. Once the problem has been resolved, you can publish another + point release that relaxes the dependency back to a caret requirement. +* If it looks like the third-party project is unable or unwilling to yank the + release, then one option is to update your code to be compatible with the + changes, and update the dependency requirement to set the minimum version to + the new release. You will also need to consider if this is a SemVer-breaking + change of your own library, for example if it exposes types from the + dependency. + diff --git a/src/doc/src/reference/semver.md b/src/doc/src/reference/semver.md new file mode 100644 index 00000000000..d4a8b7979c2 --- /dev/null +++ b/src/doc/src/reference/semver.md @@ -0,0 +1,1321 @@ +# SemVer Compatibility + +This chapter provides details on what is conventionally considered a +compatible or breaking SemVer change for new releases of a package. See the +[SemVer compatibility] section for details on what SemVer is, and how Cargo +uses it to ensure compatibility of libraries. + +These are only *guidelines*, and not necessarily hard-and-fast rules that all +projects will obey. The [Change categories] section details how this guide +classifies the level and severity of a change. Most of this guide focuses on +changes that will cause `cargo` and `rustc` to fail to build something that +previously worked. Almost every change carries some risk that it will +negatively affect the runtime behavior, and for those cases it is usually a +judgment call by the project maintainers whether or not it is a +SemVer-incompatible change. + +See also [rust-semverver], which is an experimental tool that attempts to +programmatically check compatibility rules. + +[Change categories]: #change-categories +[rust-semverver]: https://github.com/rust-dev-tools/rust-semverver +[SemVer compatibility]: resolver.md#semver-compatibility + +## Change categories + +All of the policies listed below are categorized by the level of change: + +* **Major change**: a change that requires a major SemVer bump. +* **Minor change**: a change that requires only a minor SemVer bump. +* **Possibly-breaking change**: a change that some projects may consider major + and others consider minor. + +The "Possibly-breaking" category covers changes that have the *potential* to +break during an update, but may not necessarily cause a breakage. The impact +of these changes should be considered carefully. The exact nature will depend +on the change and the principles of the project maintainers. + +Some projects may choose to only bump the patch number on a minor change. It +is encouraged to follow the SemVer spec, and only apply bug fixes in patch +releases. However, a bug fix may require an API change that is marked as a +"minor change", and shouldn't affect compatibility. This guide does not take a +stance on how each individual "minor change" should be treated, as the +difference between minor and patch changes are conventions that depend on the +nature of the change. + +Some changes are marked as "minor", even though they carry the potential risk +of breaking a build. This is for situations where the potential is extremely +low, and the potentially breaking code is unlikely to be written in idiomatic +Rust, or is specifically discouraged from use. + +This guide uses the terms "major" and "minor" assuming this relates to a +"1.0.0" release or later. Initial development releases starting with "0.y.z" +can treat changes in "y" as a major release, and "z" as a minor release. +"0.0.z" releases are always major changes. This is because Cargo uses the +convention that only changes in the left-most non-zero component are +considered incompatible. + +* API compatibility + * Items + * [Major: renaming/moving/removing any public items](#item-remove) + * [Minor: adding new public items](#item-new) + * Structs + * [Major: adding a private struct field when all current fields are public](#struct-add-private-field-when-public) + * [Major: adding a public field when no private field exists](#struct-add-public-field-when-no-private) + * [Minor: adding or removing private fields when at least one already exists](#struct-private-fields-with-private) + * [Minor: going from a tuple struct with all private fields (with at least one field) to a normal struct, or vice versa](#struct-tuple-normal-with-private) + * Enums + * [Major: adding new enum variants (without `non_exhaustive`)](#enum-variant-new) + * [Major: adding new fields to an enum variant](#enum-fields-new) + * Traits + * [Major: adding a non-defaulted trait item](#trait-new-item-no-default) + * [Major: any change to trait item signatures](#trait-item-signature) + * [Possibly-breaking: adding a defaulted trait item](#trait-new-default-item) + * [Major: adding a trait item that makes the trait non-object safe](#trait-object-safety) + * [Major: adding a type parameter without a default](#trait-new-parameter-no-default) + * [Minor: adding a defaulted trait type parameter](#trait-new-parameter-default) + * Implementations + * [Possibly-breaking change: adding any inherent items](#impl-item-new) + * Generics + * [Major: tightening generic bounds](#generic-bounds-tighten) + * [Minor: loosening generic bounds](#generic-bounds-loosen) + * [Minor: adding defaulted type parameters](#generic-new-default) + * [Minor: generalizing a type to use generics (with identical types)](#generic-generalize-identical) + * [Major: generalizing a type to use generics (with possibly different types)](#generic-generalize-different) + * [Minor: changing a generic type to a more generic type](#generic-more-generic) + * Functions + * [Major: adding/removing function parameters](#fn-change-arity) + * [Possibly-breaking: introducing a new function type parameter](#fn-generic-new) + * [Minor: generalizing a function to use generics (supporting original type)](#fn-generalize-compatible) + * [Major: generalizing a function to use generics with type mismatch](#fn-generalize-mismatch) + * Attributes + * [Major: switching from `no_std` support to requiring `std`](#attr-no-std-to-std) +* Tooling and environment compatibility + * [Possibly-breaking: changing the minimum version of Rust required](#env-new-rust) + * [Possibly-breaking: changing the platform and environment requirements](#env-change-requirements) + * Cargo + * [Minor: adding a new Cargo feature](#cargo-feature-add) + * [Major: removing a Cargo feature](#cargo-feature-remove) + * [Possibly-breaking: removing an optional dependency](#cargo-remove-opt-dep) + * [Minor: changing dependency features](#cargo-change-dep-feature) + * [Minor: adding dependencies](#cargo-dep-add) +* [Application compatibility](#application-compatibility) + +## API compatibility + +All of the examples below contain three parts: the original code, the code +after it has been modified, and an example usage of the code that could appear +in another project. In a minor change, the example usage should successfully +build with both the before and after versions. + + +### Major: renaming/moving/removing any public items + +The absence of a publicly exposed [item][items] will cause any uses of that item to +fail to compile. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// After +// ... item has been removed + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + updated_crate::foo(); // Error: cannot find function `foo` +} +``` + +Mitigating strategies: +* Mark items to be removed as [deprecated], and then remove them at a later + date in a SemVer-breaking release. +* Mark renamed items as [deprecated], and use a [`pub use`] item to re-export + to the old name. + + +### Minor: adding new public items + +Adding new, public [items] is a minor change. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +// ... absence of item + +/////////////////////////////////////////////////////////// +// After +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +// `foo` is not used since it didn't previously exist. +``` + +Note that in some rare cases this can be a **breaking change** due to glob +imports. For example, if you add a new trait, and a project has used a glob +import that brings that trait into scope, and the new trait introduces an +associated item that conflicts with any types it is implemented on, this can +cause a compile-time error due to the ambiguity. Example: + +```rust,ignore +// Breaking change example + +/////////////////////////////////////////////////////////// +// Before +// ... absence of trait + +/////////////////////////////////////////////////////////// +// After +pub trait NewTrait { + fn foo(&self) {} +} + +impl NewTrait for i32 {} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::*; + +pub trait LocalTrait { + fn foo(&self) {} +} + +impl LocalTrait for i32 {} + +fn main() { + 123i32.foo(); // Error: multiple applicable items in scope +} +``` + +This is not considered a major change because conventionally glob imports are +a known forwards-compatibility hazard. Glob imports of items from external +crates should be avoided. + + +### Major: adding a private struct field when all current fields are public + +When a private field is added to a struct that previously had all public fields, +this will break any code that attempts to construct it with a [struct literal]. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo { + pub f1: i32, +} + +/////////////////////////////////////////////////////////// +// After +pub struct Foo { + pub f1: i32, + f2: i32, +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + let x = updated_crate::Foo { f1: 123 }; // Error: missing field `f2` +} +``` + +Mitigation strategies: +* Do not add new fields to all-public field structs. +* Mark structs as [`#[non_exhaustive]`][non_exhaustive] when first introducing + an struct to prevent users from using struct literal syntax, and instead + provide a constructor method and/or [Default] implementation. + + +### Major: adding a public field when no private field exists + +When a public field is added to a struct that has all public fields, this will +break any code that attempts to construct it with a [struct literal]. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo { + pub f1: i32, +} + +/////////////////////////////////////////////////////////// +// After +pub struct Foo { + pub f1: i32, + pub f2: i32, +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + let x = updated_crate::Foo { f1: 123 }; // Error: missing field `f2` +} +``` + +Mitigation strategies: +* Do not add new new fields to all-public field structs. +* Mark structs as [`#[non_exhaustive]`][non_exhaustive] when first introducing + an struct to prevent users from using struct literal syntax, and instead + provide a constructor method and/or [Default] implementation. + + +### Minor: adding or removing private fields when at least one already exists + +It is safe to add or remove private fields from a struct when the struct +already has at least one private field. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +#[derive(Default)] +pub struct Foo { + f1: i32, +} + +/////////////////////////////////////////////////////////// +// After +#[derive(Default)] +pub struct Foo { + f2: f64, +} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +fn main() { + // Cannot access private fields. + let x = updated_crate::Foo::default(); +} +``` + +This is safe because existing code cannot use a [struct literal] to construct +it, nor exhaustively match its contents. + +Note that for tuple structs, this is a **major change** if the tuple contains +public fields, and the addition or removal of a private field changes the +index of any public field. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +#[derive(Default)] +pub struct Foo(pub i32, i32); + +/////////////////////////////////////////////////////////// +// After +#[derive(Default)] +pub struct Foo(f64, pub i32, i32); + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + let x = updated_crate::Foo::default(); + let y = x.0; // Error: is private +} +``` + + +### Minor: going from a tuple struct with all private fields (with at least one field) to a normal struct, or vice versa + +Changing a tuple struct to a normal struct (or vice-versa) is safe if all +fields are private. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +#[derive(Default)] +pub struct Foo(i32); + +/////////////////////////////////////////////////////////// +// After +#[derive(Default)] +pub struct Foo { + f1: i32, +} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +fn main() { + // Cannot access private fields. + let x = updated_crate::Foo::default(); +} +``` + +This is safe because existing code cannot use a [struct literal] to construct +it, nor match its contents. + + +### Major: adding new enum variants (without `non_exhaustive`) + +It is a breaking change to add a new enum variant if the enum does not use the +[`#[non_exhaustive]`][non_exhaustive] attribute. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub enum E { + Variant1, +} + +/////////////////////////////////////////////////////////// +// After +pub enum E { + Variant1, + Variant2, +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + use updated_crate::E; + let x = E::Variant1; + match x { // Error: `Variant2` not covered + E::Variant1 => {} + } +} +``` + +Mitigation strategies: +* When introducing the enum, mark it as [`#[non_exhaustive]`][non_exhaustive] + to force users to use [wildcard patterns] to catch new variants. + + +### Major: adding new fields to an enum variant + +It is a breaking change to add new fields to an enum variant because all +fields are public, and constructors and matching will fail to compile. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub enum E { + Variant1 { f1: i32 }, +} + +/////////////////////////////////////////////////////////// +// After +pub enum E { + Variant1 { f1: i32, f2: i32 }, +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + use updated_crate::E; + let x = E::Variant1 { f1: 1 }; // Error: missing f2 + match x { + E::Variant1 { f1 } => {} // Error: missing f2 + } +} +``` + +Mitigation strategies: +* When introducing the enum, mark the variant as [`non_exhaustive`][non_exhaustive] + so that it cannot be constructed or matched without wildcards. + ```rust,ignore,skip + pub enum E { + #[non_exhaustive] + Variant1{f1: i32} + } + ``` +* When introducing the enum, use an explicit struct as a value, where you can + have control over the field visibility. + ```rust,ignore,skip + pub struct Foo { + f1: i32, + f2: i32, + } + pub enum E { + Variant1(Foo) + } + ``` + + +### Major: adding a non-defaulted trait item + +It is a breaking change to add a non-defaulted item to a trait. This will +break any implementors of the trait. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait { + fn foo(&self); +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Trait; +struct Foo; + +impl Trait for Foo {} // Error: not all trait items implemented +``` + +Mitigation strategies: +* Always provide a default implementation or value for new associated trait + items. +* When introducing the trait, use the [sealed trait] technique to prevent + users outside of the crate from implementing the trait. + + +### Major: any change to trait item signatures + +It is a breaking change to make any change to a trait item signature. This can +break external implementors of the trait. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait { + fn f(&self, x: i32) {} +} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait { + // For sealed traits or normal functions, this would be a minor change + // because generalizing with generics strictly expands the possible uses. + // But in this case, trait implementations must use the same signature. + fn f(&self, x: V) {} +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Trait; +struct Foo; + +impl Trait for Foo { + fn f(&self, x: i32) {} // Error: trait declaration has 1 type parameter +} +``` + +Mitigation strategies: +* Introduce new items with default implementations to cover the new + functionality instead of modifying existing items. +* When introducing the trait, use the [sealed trait] technique to prevent + users outside of the crate from implementing the trait. + + +### Possibly-breaking: adding a defaulted trait item + +It is usually safe to add a defaulted trait item. However, this can sometimes +cause a compile error. For example, this can introduce an ambiguity if a +method of the same name exists in another trait. + +```rust,ignore +// Breaking change example + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait { + fn foo(&self) {} +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Trait; +struct Foo; + +trait LocalTrait { + fn foo(&self) {} +} + +impl Trait for Foo {} +impl LocalTrait for Foo {} + +fn main() { + let x = Foo; + x.foo(); // Error: multiple applicable items in scope +} +``` + +Note that this ambiguity does *not* exist for name collisions on [inherent +implementations], as they take priority over trait items. + +See [trait-object-safety](#trait-object-safety) for a special case to consider +when adding trait items. + +Mitigation strategies: +* Some projects may deem this acceptable breakage, particularly if the new + item name is unlikely to collide with any existing code. Choose names + carefully to help avoid these collisions. Additionally, it may be acceptable + to require downstream users to add [disambiguation syntax] to select the + correct function when updating the dependency. + + +### Major: adding a trait item that makes the trait non-object safe + +It is a breaking change to add a trait item that changes the trait to not be +[object safe]. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait { + // An associated const makes the trait not object-safe. + const CONST: i32 = 123; +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Trait; +struct Foo; + +impl Trait for Foo {} + +fn main() { + let obj: Box = Box::new(Foo); // Error: cannot be made into an object +} +``` + +It is safe to do the converse (making a non-object safe trait into a safe +one). + + +### Major: adding a type parameter without a default + +It is a breaking change to add a type parameter without a default to a trait. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Trait; +struct Foo; + +impl Trait for Foo {} // Error: wrong number of type arguments +``` + +Mitigating strategies: +* See [adding a defaulted trait type parameter](#trait-new-parameter-default). + + +### Minor: adding a defaulted trait type parameter + +It is safe to add a type parameter to a trait as long as it has a default. +External implementors will use the default without needing to specify the +parameter. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait {} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::Trait; +struct Foo; + +impl Trait for Foo {} +``` + + +### Possibly-breaking change: adding any inherent items + +Usually adding inherent items to an implementation should be safe because +inherent items take priority over trait items. However, in some cases the +collision can cause problems if the name is the same as an implemented trait +item with a different signature. + +```rust,ignore +// Breaking change example + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo; + +/////////////////////////////////////////////////////////// +// After +pub struct Foo; + +impl Foo { + pub fn foo(&self) {} +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Foo; + +trait Trait { + fn foo(&self, x: i32) {} +} + +impl Trait for Foo {} + +fn main() { + let x = Foo; + x.foo(1); // Error: this function takes 0 arguments +} +``` + +Note that if the signatures match, there would not be a compile-time error, +but possibly a silent change in runtime behavior (because it is now executing +a different function). + +Mitigation strategies: +* Some projects may deem this acceptable breakage, particularly if the new + item name is unlikely to collide with any existing code. Choose names + carefully to help avoid these collisions. Additionally, it may be acceptable + to require downstream users to add [disambiguation syntax] to select the + correct function when updating the dependency. + + +### Major: tightening generic bounds + +It is a breaking change to tighten generic bounds on a type since this can +break users expecting the looser bounds. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo { + pub f1: A, +} + +/////////////////////////////////////////////////////////// +// After +pub struct Foo { + pub f1: A, +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Foo; + +fn main() { + let s = Foo { f1: 1.23 }; // Error: the trait bound `{float}: std::cmp::Eq` is not satisfied +} +``` + + +### Minor: loosening generic bounds + +It is safe to loosen the generic bounds on a type, as it only expands what is +allowed. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo { + pub f1: A, +} + +/////////////////////////////////////////////////////////// +// After +pub struct Foo { + pub f1: A, +} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::Foo; + +fn main() { + let s = Foo { f1: 123 }; +} +``` + + +### Minor: adding defaulted type parameters + +It is safe to add a type parameter to a type as long as it has a default. All +existing references will use the default without needing to specify the +parameter. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +#[derive(Default)] +pub struct Foo {} + +/////////////////////////////////////////////////////////// +// After +#[derive(Default)] +pub struct Foo { + f1: A, +} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::Foo; + +fn main() { + let s: Foo = Default::default(); +} +``` + + +### Minor: generalizing a type to use generics (with identical types) + +A struct or enum field can change from a concrete type to a generic type +parameter, provided that the change results in an identical type for all +existing use cases. For example, the following change is permitted: + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo(pub u8); + +/////////////////////////////////////////////////////////// +// After +pub struct Foo(pub T); + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::Foo; + +fn main() { + let s: Foo = Foo(123); +} +``` + +because existing uses of `Foo` are shorthand for `Foo` which yields the +identical field type. + + +### Major: generalizing a type to use generics (with possibly different types) + +Changing a struct or enum field from a concrete type to a generic type +parameter can break if the type can change. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo(pub T, pub u8); + +/////////////////////////////////////////////////////////// +// After +pub struct Foo(pub T, pub T); + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::Foo; + +fn main() { + let s: Foo = Foo(3.14, 123); // Error: mismatched types +} +``` + + +### Minor: changing a generic type to a more generic type + +It is safe to change a generic type to a more generic one. For example, the +following adds a generic parameter that defaults to the original type, which +is safe because all existing users will be using the same type for both +fields, the the defaulted parameter does not need to be specified. + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub struct Foo(pub T, pub T); + +/////////////////////////////////////////////////////////// +// After +pub struct Foo(pub T, pub U); + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::Foo; + +fn main() { + let s: Foo = Foo(1.0, 2.0); +} +``` + + +### Major: adding/removing function parameters + +Changing the arity of a function is a breaking change. + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// After +pub fn foo(x: i32) {} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +fn main() { + updated_crate::foo(); // Error: this function takes 1 argument +} +``` + +Mitigating strategies: +* Introduce a new function with the new signature and possibly + [deprecate][deprecated] the old one. +* Introduce functions that take a struct argument, where the struct is built + with the builder pattern. This allows new fields to be added to the struct + in the future. + + +### Possibly-breaking: introducing a new function type parameter + +Usually, adding a non-defaulted type parameter is safe, but in some +cases it can be a breaking change: + +```rust,ignore +// Breaking change example + +/////////////////////////////////////////////////////////// +// Before +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// After +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::foo; + +fn main() { + foo::(); // Error: wrong number of type arguments +} +``` + +However, such explicit calls are rare enough (and can usually be written in +other ways) that this breakage is usually acceptable. One should take into +account how likely it is that the function in question is being called with +explicit type arguments. + + +### Minor: generalizing a function to use generics (supporting original type) + +The type of an parameter to a function, or its return value, can be +*generalized* to use generics, including by introducing a new type parameter, +as long as it can be instantiated to the original type. For example, the +following changes are allowed: + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub fn foo(x: u8) -> u8 { + x +} +pub fn bar>(t: T) {} + +/////////////////////////////////////////////////////////// +// After +use std::ops::Add; +pub fn foo(x: T) -> T { + x +} +pub fn bar>(t: T) {} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::{bar, foo}; + +fn main() { + foo(1); + bar(vec![1, 2, 3].into_iter()); +} +``` + +because all existing uses are instantiations of the new signature. + +Perhaps somewhat surprisingly, generalization applies to trait objects as +well, given that every trait implements itself: + +```rust,ignore +// MINOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub trait Trait {} +pub fn foo(t: &dyn Trait) {} + +/////////////////////////////////////////////////////////// +// After +pub trait Trait {} +pub fn foo(t: &T) {} + +/////////////////////////////////////////////////////////// +// Example use of the library that will safely work. +use updated_crate::{foo, Trait}; + +struct Foo; +impl Trait for Foo {} + +fn main() { + let obj = Foo; + foo(&obj); +} +``` + +(The use of `?Sized` is essential; otherwise you couldn't recover the original +signature.) + +Introducing generics in this way can potentially create type inference +failures. These are usually rare, and may be acceptable breakage for some +projects, as this can be fixed with additional type annotations. + +```rust,ignore +// Breaking change example + +/////////////////////////////////////////////////////////// +// Before +pub fn foo() -> i32 { + 0 +} + +/////////////////////////////////////////////////////////// +// After +pub fn foo() -> T { + Default::default() +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::foo; + +fn main() { + let x = foo(); // Error: type annotations needed +} +``` + + +### Major: generalizing a function to use generics with type mismatch + +It is a breaking change to change a function parameter or return type if the +generic type constrains or changes the types previously allowed. For example, +the following adds a generic constraint that may not be satisfied by existing +code: + +```rust,ignore +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +pub fn foo(x: Vec) {} + +/////////////////////////////////////////////////////////// +// After +pub fn foo>(x: T) {} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +use updated_crate::foo; + +fn main() { + foo(vec![1, 2, 3]); // Error: `std::marker::Copy` is not implemented for `std::vec::Vec` +} +``` + + +### Major: switching from `no_std` support to requiring `std` + +If your library specifically supports a [`no_std`] environment, it is a +breaking change to make a new release that requires `std`. + +```rust,ignore,skip +// MAJOR CHANGE + +/////////////////////////////////////////////////////////// +// Before +#![no_std] +pub fn foo() {} + +/////////////////////////////////////////////////////////// +// After +pub fn foo() { + std::time::SystemTime::now(); +} + +/////////////////////////////////////////////////////////// +// Example usage that will break. +// This will fail to link for no_std targets because they don't have a `std` crate. +#![no_std] +use updated_crate::foo; + +fn example() { + foo(); +} +``` + +Mitigation strategies: +* A common idiom to avoid this is to include a `std` [Cargo feature] that + optionally enables `std` support, and when the feature is off, the library + can be used in a `no_std` environment. + +## Tooling and environment compatibility + + +### Possibly-breaking: changing the minimum version of Rust required + +Introducing the use of new features in a new release of Rust can break +projects that are using older versions of Rust. This also includes using new +features in a new release of Cargo, and requiring the use of a nightly-only +feature in a crate that previously worked on stable. + +Some projects choose to allow this in a minor release for various reasons. It +is usually relatively easy to update to a newer version of Rust. Rust also has +a rapid 6-week release cycle, and some projects will provide compatibility +within a window of releases (such as the current stable release plus N +previous releases). Just keep in mind that some large projects may not be able +to update their Rust toolchain rapidly. + +Mitigation strategies: +* Use [Cargo features] to make the new features opt-in. +* Provide a large window of support for older releases. +* Copy the source of new standard library items if possible so that you + can continue to use an older version but take advantage of the new feature. +* Provide a separate branch of older minor releases that can receive backports + of important bugfixes. +* Keep an eye out for the [`[cfg(version(..))]`][cfg-version] and + [`#[cfg(accessible(..))]`][cfg-accessible] features which provide an opt-in + mechanism for new features. These are currently unstable and only available + in the nightly channel. + + +### Possibly-breaking: changing the platform and environment requirements + +There is a very wide range of assumptions a library makes about the +environment that it runs in, such as the host platform, operating system +version, available services, filesystem support, etc. It can be a breaking +change if you make a new release that restricts what was previously supported, +for example requiring a newer version of an operating system. These changes +can be difficult to track, since you may not always know if a change breaks in +an environment that is not automatically tested. + +Some projects may deem this acceptable breakage, particularly if the breakage +is unlikely for most users, or the project doesn't have the resources to +support all environments. Another notable situation is when a vendor +discontinues support for some hardware or OS, the project may deem it +reasonable to also discontinue support. + +Mitigation strategies: +* Document the platforms and environments you specifically support. +* Test your code on a wide range of environments in CI. + +### Cargo + + +#### Minor: adding a new Cargo feature + +It is usually safe to add new [Cargo features]. If the feature introduces new +changes that cause a breaking change, this can cause difficulties for projects +that have stricter backwards-compatibility needs. In that scenario, avoid +adding the feature to the "default" list, and possibly document the +consequences of enabling the feature. + +```toml +# MINOR CHANGE + +########################################################### +# Before +[features] +# ..empty + +########################################################### +# After +[features] +std = [] +``` + + +#### Major: removing a Cargo feature + +It is usually a breaking change to remove [Cargo features]. This will cause +an error for any project that enabled the feature. + +```toml +# MAJOR CHANGE + +########################################################### +# Before +[features] +logging = [] + +########################################################### +# After +[dependencies] +# ..logging removed +``` + +Mitigation strategies: +* Clearly document your features. If there is an internal or experimental + feature, mark it as such, so that users know the status of the feature. +* Leave the old feature in `Cargo.toml`, but otherwise remove its + functionality. Document that the feature is deprecated, and remove it in a + future major SemVer release. + + +#### Possibly-breaking: removing an optional dependency + +Removing an optional dependency can break a project using your library because +another project may be enabling that dependency via [Cargo features]. + +```toml +# Breaking change example + +########################################################### +# Before +[dependencies] +curl = { version = "0.4.31", optional = true } + +########################################################### +# After +[dependencies] +# ..curl removed +``` + +Mitigation strategies: +* Clearly document your features. If the optional dependency is not included + in the documented list of features, then you may decide to consider it safe + to change undocumented entries. +* Leave the optional dependency, and just don't use it within your library. +* Replace the optional dependency with a [Cargo feature] that does nothing, + and document that it is deprecated. +* Use high-level features which enable optional dependencies, and document + those as the preferred way to enable the extended functionality. For + example, if your library has optional support for something like + "networking", create a generic feature name "networking" that enables the + optional dependencies necessary to implement "networking". Then document the + "networking" feature. + + +#### Minor: changing dependency features + +It is usually safe to change the features on a dependency, as long as the +feature does not introduce a breaking change. + +```toml +# MINOR CHANGE + +########################################################### +# Before +[dependencies] +rand = { version = "0.7.3", features = ["small_rng"] } + + +########################################################### +# After +[dependencies] +rand = "0.7.3" +``` + + +#### Minor: adding dependencies + +It is usually safe to add new dependencies, as long as the new dependency +does not introduce new requirements that result in a breaking change. +For example, adding a new dependency that requires nightly in a project +that previously worked on stable is a major change. + +```toml +# MINOR CHANGE + +########################################################### +# Before +[dependencies] +# ..empty + +########################################################### +# After +[dependencies] +log = "0.4.11" +``` + +## Application compatibility + +Cargo projects may also include executable binaries which have their own +interfaces (such as a CLI interface, OS-level interaction, etc.). Since these +are part of the Cargo package, they often use and share the same version as +the package. You will need to decide if and how you want to employ a SemVer +contract with your users in the changes you make to your application. The +potential breaking and compatible changes to an application are too numerous +to list, so you are encouraged to use the spirit of the [SemVer] spec to guide +your decisions on how to apply versioning to your application, or at least +document what your commitments are. + +[`no_std`]: ../../reference/crates-and-source-files.html#preludes-and-no_std +[`pub use`]: ../../reference/items/use-declarations.html +[Cargo feature]: features.md +[Cargo features]: features.md +[cfg-accessible]: https://github.com/rust-lang/rust/issues/64797 +[cfg-version]: https://github.com/rust-lang/rust/issues/64796 +[Default]: ../../std/default/trait.Default.html +[deprecated]: ../../reference/attributes/diagnostics.html#the-deprecated-attribute +[disambiguation syntax]: ../../reference/expressions/call-expr.html#disambiguating-function-calls +[inherent implementations]: ../../reference/items/implementations.html#inherent-implementations +[items]: ../../reference/items.html +[non_exhaustive]: ../../reference/attributes/type_system.html#the-non_exhaustive-attribute +[object safe]: ../../reference/items/traits.html#object-safety +[rust-feature]: https://doc.rust-lang.org/nightly/unstable-book/ +[sealed trait]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed +[SemVer]: https://semver.org/ +[struct literal]: ../../reference/expressions/struct-expr.html +[wildcard patterns]: ../../reference/patterns.html#wildcard-pattern From 4d023b1ec5ffce5951d56182cd054bb22a69c02b Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 10 Aug 2020 11:29:28 -0700 Subject: [PATCH 2/4] Don't check src/doc directory. --- tests/internal.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/internal.rs b/tests/internal.rs index 13ab8af23f2..f6d9e5e16c0 100644 --- a/tests/internal.rs +++ b/tests/internal.rs @@ -6,11 +6,18 @@ fn check_forbidden_code() { // Do not use certain macros, functions, etc. if !cargo::util::is_ci() { // Only check these on CI, otherwise it could be annoying. + use std::io::Write; + writeln!( + std::io::stderr(), + "\nSkipping check_forbidden_code test, set CI=1 to enable" + ) + .unwrap(); return; } - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); - for entry in walkdir::WalkDir::new(path) + let root_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + for entry in walkdir::WalkDir::new(&root_path) .into_iter() + .filter_entry(|e| e.path() != root_path.join("doc")) .filter_map(|e| e.ok()) { let path = entry.path(); @@ -22,6 +29,7 @@ fn check_forbidden_code() { { continue; } + eprintln!("checking {}", path.display()); let c = fs::read_to_string(path).unwrap(); for (line_index, line) in c.lines().enumerate() { if line.trim().starts_with("//") { From 1ee38a326e56723b54da2adb8dd7d9af43fae744 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 10 Aug 2020 11:33:42 -0700 Subject: [PATCH 3/4] Try to fix CI for semver-check. --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6b762187b4c..e6926d798eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,7 +92,8 @@ jobs: - run: rustup update nightly && rustup default nightly - run: rustup component add rust-docs - run: ci/validate-man.sh - - run: cd src/doc/semver-check && cargo run + # This requires rustfmt, use stable. + - run: cd src/doc/semver-check && cargo +stable run - run: | mkdir mdbook curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.3.7/mdbook-v0.3.7-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook From 006023c5827b2288230df51676b69b725b822e86 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 10 Aug 2020 17:35:34 -0700 Subject: [PATCH 4/4] Update from review comments. --- src/doc/src/reference/resolver.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/doc/src/reference/resolver.md b/src/doc/src/reference/resolver.md index ba82659afa9..029f6b90e79 100644 --- a/src/doc/src/reference/resolver.md +++ b/src/doc/src/reference/resolver.md @@ -213,7 +213,10 @@ the [`perf` feature], then the oldest version it can select is `1.3.0`, because versions prior to that did not contain the `perf` feature. Similarly, if a feature is removed from a new release, then packages that require that feature will be stuck on the older releases that contain that feature. It is -discouraged to remove features in a SemVer-compatible release. +discouraged to remove features in a SemVer-compatible release. Beware that +optional dependencies also define an implicit feature, so removing an optional +dependency or making it non-optional can cause problems, see [removing an +optional dependency]. [`im`]: https://crates.io/crates/im [`perf` feature]: https://github.com/rust-lang/regex/blob/1.3.0/Cargo.toml#L56 @@ -221,17 +224,20 @@ discouraged to remove features in a SemVer-compatible release. [`regex`]: https://crates.io/crates/regex [`serde` dependency]: https://github.com/bodil/im-rs/blob/v15.0.0/Cargo.toml#L46 [features]: features.md +[removing an optional dependency]: semver.md#cargo-remove-opt-dep [workspace]: workspaces.md ### `links` The [`links` field] is used to ensure only one copy of a native library is -linked into a binary. It is an error if multiple SemVer-incompatible versions -of a package with a `links` field appears in the resolve graph. For example, -it is an error if one package depends on [`libgit2-sys`] version `0.11` and -another depends on `0.12`, because Cargo is unable to unify those, but they -both link to the `git2` native library. Due to this rigid requirement, it is -encouraged to be very careful when making SemVer-incompatible releases with +linked into a binary. The resolver will attempt to find a graph where there is +only one instance of each `links` name. If it is unable to find a graph that +satisfies that constraint, it will return an error. + +For example, it is an error if one package depends on [`libgit2-sys`] version +`0.11` and another depends on `0.12`, because Cargo is unable to unify those, +but they both link to the `git2` native library. Due to this requirement, it +is encouraged to be very careful when making SemVer-incompatible releases with the `links` field if your library is in common use. [`links` field]: manifest.md#the-links-field