-
Notifications
You must be signed in to change notification settings - Fork 4.5k
docs: Update backwards compatibility policy for Rust SDK #32655
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
@@ -11,16 +11,17 @@ 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 3 | ||
`MINOR` releases, and following 3 `MINOR` releases, accounting for skipped minor | ||
versions, more details below. | ||
|
||
### 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 4 `MINOR` releases later, deprecated features may be removed in an incompatible way. | ||
|
||
### Release Cadence | ||
|
||
|
@@ -30,7 +31,7 @@ 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 | ||
|
||
|
@@ -40,6 +41,10 @@ updates of a particular `MINOR` version release. | |
deprecated features. Client SDKs and tooling will begin using new features and endpoints | ||
that were enabled in the previous `MAJOR` version. | ||
|
||
The Rust SDK will not have any future `MAJOR` releases, and will stay on `1.X` to | ||
provide a better developer experience. More details in | ||
[Why not just use semver](#why-not-just-use-semver). | ||
|
||
#### Minor Releases (1.x.0) | ||
|
||
New features and proposal implementations are added to _new_ `MINOR` version | ||
|
@@ -48,6 +53,20 @@ 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. According to code upgrade difficulty, every `MINOR` release | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
will be compatible with at least the 3 preceding `MINOR` releases and 3 future | ||
`MINOR` releases. | ||
|
||
For example, software using version `1.20` will be compatible with versions up | ||
to `1.23` going forward, and back to `1.17`. Any release numbers that are skipped, | ||
such as `1.15`, are not considered in this calculation. | ||
|
||
At a projected cadence of 4 minor releases per year, that gives developers at least | ||
one year to update a project. | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
|
@@ -87,12 +106,13 @@ Patch releases: | |
Minor releases: | ||
|
||
- New APIs | ||
|
||
Major releases | ||
|
||
- Removal of deprecated APIs | ||
- Backwards incompatible behavior changes | ||
joncinque marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Major releases: | ||
|
||
- Are not projected to happen ever. More details in [Why not just use semver](#why-not-just-use-semver). | ||
|
||
### CLI Tools | ||
|
||
Patch releases: | ||
|
@@ -155,3 +175,116 @@ 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 use a model for introducing breaking changes that does | ||
*not* follow semantic versioning (semver). | ||
|
||
#### 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's very difficult to pick up a breaking | ||
change accidentally. The default dependency declaration in Cargo follows this | ||
concept. 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. | ||
|
||
Additionally, 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. | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Additionally, the types in `solana-program` used by both your crate and your | ||
dependency are treated as the same. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not true. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll remove this whole part as you suggested previously, since there's no need for us to describe cargo |
||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally, programs are not used as dependencies :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So if we want to be practical, |
||
|
||
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. | ||
|
||
#### So what's your solution? | ||
|
||
Rather than forcing developers to disentangle these kinds of messes on every | ||
major version release, our solution is to keep everything on the same major version. | ||
|
||
However, to avoid chaos and ensure that no downstream client ever breaks from updating | ||
from one `MINOR` version to the next, we must provide new functions or structs | ||
and deprecate the old ones. | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
We have broken this commitment in the past, but through expanded downstream | ||
testing and conscientiously exposing APIs, we aim to never repeat that mistake. | ||
If it does happen accidentally, we will do whatever is necessary to restore | ||
the functionality, within reason. | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
To avoid maintaining too much old code, however, we reserve the right to remove | ||
deprecated code after at least one year, or roughly 4 `MINOR` versions later. | ||
|
||
This model has precedents in the Rust ecosystem. Through | ||
[Rust editions](https://blog.rust-lang.org/2021/05/11/edition-2021.html#what-is-an-edition), | ||
the Rust developers can introduce backwards-incompatible changes, all while staying | ||
on major version 1. As crate developers, we have less flexibility than a language, | ||
but the concept is similar. | ||
ilya-bobyr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 some stability while avoiding stagnation. | ||
joncinque marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### But why was the upgrade to 1.16 so difficult? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can understand how this is close to your heart, but do you really think this discussion belongs to the policy doc? :P We have issues with all the details, and going forward it would not matter any more, I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah you're absolutely right 😅 I wanted to provide some historical record so we know what motivated this change. What do you think about adding it as a post-mortem in #32503 ? |
||
|
||
In version `1.16` of the Rust crates, the Borsh dependency was upgraded from | ||
`0.9` to `0.10`, which constituted a breaking change. | ||
|
||
Downstream developers faced unsolvable resolution problems because their dependencies | ||
were also forced to use `1.16`, but if the dependencies were not explicitly updated | ||
to use Borsh `0.10`, they were met with incompatible traits, since they implemented | ||
`BorshSerialize` and `BorshDerialize` on version 0.9, which as described earlier, | ||
joncinque marked this conversation as resolved.
Show resolved
Hide resolved
|
||
are treated as completely different traits from `BorshSerialize` and `BorshDeserialize` | ||
on version 0.10. | ||
|
||
To address this problem, we've implemented the older Borsh 0.9 traits on required | ||
joncinque marked this conversation as resolved.
Show resolved
Hide resolved
|
||
types, and exposed the Solana Borsh utilities through explicit versioning. This | ||
allows us to upgrade to any other version of Borsh without ever breaking a | ||
downstream developer. | ||
|
||
In a year, however, we reserve the right to remove the implementations of the | ||
Borsh 0.9 traits and potentially break downstream projects that have not updated | ||
joncinque marked this conversation as resolved.
Show resolved
Hide resolved
|
||
within the last year. | ||
|
||
Even if the Rust crates implemented proper semantic versioning, downstream | ||
projects would have been broken as soon as they tried to mix `Pubkey`s from | ||
a non-updated dependency with `Pubkey`s from the new major version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is a bit too restrictive.
There seems to be no reason to say we will never increase the major release number.
It does seem quite unlikely at the moment.
But things may change.
We do want to be explicit about the currently chosen default policy of staying on
1.x
.So we can, maybe, say something along the lines of:
If you think it is too subtle, it is probably fine to keep the more assertive "no
MAJOR
version updates" statement as well.Also, this paragraph seems to contradict the previous one, that explain when happens in a major version update. If we are saying they never happen, there is no point in this explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I think I was a little too inspired by our chats.
And you're right about the contradiction -- this document also covers RPC endpoints, and I didn't want to have the policy apply to both RPC and SDK.
I'll make it clearer which parts cover RPC and which parts cover the SDK.