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

docs: Update backwards compatibility policy for Rust SDK #32655

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 101 additions & 13 deletions docs/src/developing/backwards-compatibility.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Backward Compatibility Policy
title: Backwards Compatibility Policy
---

As the Solana developer ecosystem grows, so does the need for clear expectations around
Expand All @@ -11,16 +11,16 @@ and so this document attempts to clarify and codify the process for new releases
### Expectations

- Solana software releases include APIs, SDKs, and CLI tooling (with a few [exceptions](#exceptions)).
- Solana software releases follow semantic versioning, more details below.
- Software for a `MINOR` version release will be compatible across all software on the
same `MAJOR` version.
- Solana software releases *do not always* follow semantic versioning, more details below.
- Software for a `MINOR` version release will be compatible with the previous
`MINOR` releases, and following `MINOR` releases, for at least one year.

### Deprecation Process

1. In any `PATCH` or `MINOR` release, a feature, API, endpoint, etc. could be marked as deprecated.
2. According to code upgrade difficulty, some features will be remain deprecated for a few release
2. According to code upgrade difficulty, some features will remain deprecated for a few release
cycles.
3. In a future `MAJOR` release, deprecated features will be removed in an incompatible way.
3. At least one year later, deprecated features may be removed in an incompatible way.

### Release Cadence

Expand All @@ -30,15 +30,18 @@ updates of a particular `MINOR` version release.

#### Release Channels

- `edge` software that contains cutting-edge features with no backward compatibility policy
- `edge` software that contains cutting-edge features with no backwards compatibility policy
- `beta` software that runs on the Solana Testnet cluster
- `stable` software that run on the Solana Mainnet Beta and Devnet clusters

#### Major Releases (x.0.0)

`MAJOR` version releases (e.g. 2.0.0) may contain breaking changes and removal of previously
deprecated features. Client SDKs and tooling will begin using new features and endpoints
that were enabled in the previous `MAJOR` version.
RPC `MAJOR` version releases (e.g. 2.0.0) may contain breaking changes and removal
of previously deprecated features.

We do not expect the Rust SDK `MAJOR` version to be changed. Any breaking changes
will be done though the deprecation process described in
[Why not just use SemVer](#why-not-just-use-semver) in a `MINOR` version.

#### Minor Releases (1.x.0)

Expand All @@ -48,6 +51,12 @@ on the testnet, `MINOR` versions are considered to be in the `beta` release chan
those changes have been patched as needed and proven to be reliable, the `MINOR` version will
be upgraded to the `stable` release channel and deployed to the Mainnet Beta cluster.

The Rust SDK may contain breaking changes and removal of previously deprecated features
in a `MINOR` release. Every `MINOR` release will be compatible with at least one year of
preceding `MINOR` releases, and at least one year of future `MINOR` releases.

More details in [Why not just use SemVer](#why-not-just-use-semver).

#### Patch Releases (1.0.x)

Low risk features, non-breaking changes, and security and bug fixes are shipped as part
Expand Down Expand Up @@ -87,11 +96,12 @@ Patch releases:
Minor releases:

- New APIs
- Removal of deprecated APIs, subject to deprecation delay
- Backwards incompatible behavior changes, subject to deprecation delay

Major releases
Major releases:

- Removal of deprecated APIs
- Backwards incompatible behavior changes
- Are not projected to happen ever. More details in [Why not just use SemVer](#why-not-just-use-semver).

### CLI Tools

Expand Down Expand Up @@ -155,3 +165,81 @@ circumvented in order to rapidly deploy a fix, depending on the severity of the
CLI tooling json output (`output --json`) compatibility will be preserved; however, output directed
for a human reader is subject to change. This includes output as well as potential help, warning, or
error messages.

### Why not just use SemVer

The Solana Rust SDK crates do *not* follow semantic versioning (SemVer) for
breaking changes.

#### SemVer in Rust

Under SemVer, breaking changes, such as removal of functions or types, only happens
in a new major version.

In many situations, this is useful -- it is undesirable to pick up a breaking
change accidentally. The default dependency declaration in Cargo follows SemVer.
It assumes that all minor versions are compatible, and will automatically update
to the newest minor version available.

For example, if you declare `solana-program = "1.10"`, Cargo can pull in version
`1.16`, since it assumes that all `1.X.Y` releases are compatible.

If, however, Cargo deems that two versions of a package are incompatible, it will
treat them as two completely separate packages.

Packages can be deemed incompatible for many reasons, but the most common situation
under the default declaration format (SemVer) is due to different major versions.

If a package ends up with two or more instances of `solana-program`,
[bad things will happen](https://doc.rust-lang.org/cargo/reference/resolver.html#version-incompatibility-hazards),
either at compile-time or runtime, if the conflicting types in the public APIs
are ever used together.

Because of these issues, developers should only want one instance of `solana-program`
in their project. This includes *all* direct and indirect dependencies: they should
all use the same `solana-program`.

If developers all use the default versioning declaration with major version `1`,
then we can ensure that only one version of `solana-program` exists in the resolution.

The specifics of the Cargo resolver are beyond the scope of this document.
You can find more information about the Cargo resolver in
[The Cargo Book](https://doc.rust-lang.org/cargo/reference/resolver.html).
joncinque marked this conversation as resolved.
Show resolved Hide resolved

#### So what's your solution?

We would like all releases to be 100% backwards compatible, allowing everyone
to upgrade at any time with no effort. This model, however, would put overly
strict constraints on the development of the Rust SDK crates, considering we cannot
in practice increase the major version.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand. All the reasons for a different method than semver here seems to lead to that you cannot in practice increase the major version

Why is that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was covered in a previous commit, but I removed it. Adding it here for more motivation:

Cargo will force all crates in a build to use the same major version
of a dependency. For example, if your crate my-crate declares solana-program = "1.10",
and a dependency my-dependency declares solana-program = "1.16", both your crate and your
dependency will be built with version 1.16, at least.

Additionally, the types in solana-program used by both your crate and your
dependency are treated as the same.

For example, if solana-program has a struct declared as:

#[derive(PartialEq)]
pub struct Pubkey(pub [u8; 32]);

And my-dependency has:

pub const DEPENDENCY_PUBKEY = Pubkey([1; 32]);

Then my-crate can say:

use solana_program::Pubkey;
let my_pubkey = Pubkey([1; 32]);
if my_pubkey == my_dependency::DEPENDENCY_PUBKEY {
    println!("They're the same");
}

On the other hand, types from crates with different major versions are treated as
completely different types. If my-crate declares solana-program = "2" and
my-dependency declares solana-program = "1", then the preceding code will not
compile.

To get this code to compile, you need to create functions to convert between the
types in v1 and v2. And whenever v3 is released, the entire problem becomes multiplied.

But either way, I don't think we should move forward with changing this. I'll provide an update in the main semver issue.


So we settle on a compromise. We guarantee full compatibility, but only for a
certain period of time. First we deprecate APIs, and only remove or break the
API after at least one year has passed.

We are in the process of setting up tests that verify compatibility guarantees to match
our policy. But, as any test, it may have gaps, and is subject to engineering time
invested in the test coverage.

Should we break backwards compatibility, we will do whatever is necessary to restore
functionality, within reason.

This model has precedents in many other platforms.
[Rust editions](https://blog.rust-lang.org/2021/05/11/edition-2021.html#what-is-an-edition),
can introduce backwards-incompatible changes.

And there are even more examples in
[Why Semantic Versioning Isn't](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e):

> Node doesn't follow SemVer, Rails doesn't do it, Python doesn't do it, Ruby doesn't do it, even npm doesn't follow SemVer.

The Solana SDK is closer to a platform than a library, so it makes more sense to
follow other platforms.

Additionally, the
[Minimum Supported Rust Version (MSRV) Policies](https://github.com/rust-lang/api-guidelines/discussions/231)
suggest that updating MSRV is *not* a SemVer breaking change, even though it
effectively forces users to upgrade their compiler just to update a crate.

By ensuring that projects will never immediately break, and giving developers
one year to update, this model will keep stability while avoiding stagnation.