SemVer 兼容性
本章详细说明了对于软件包的新版本,传统上认为的兼容或破坏性的 SemVer 变更。有关 SemVer 的详细信息,以及 Cargo 如何使用它来确保库的兼容性,请参阅SemVer 兼容性部分。
这些仅仅是指导原则,而不一定是所有项目都必须遵守的硬性规则。变更类别部分详细说明了本指南如何对变更的级别和严重程度进行分类。本指南的大部分内容侧重于导致 cargo
和 rustc
无法构建以前正常工作的代码的变更。几乎每个变更都存在一些负面影响运行时行为的风险,对于这些情况,通常由项目维护者来判断是否是 SemVer 不兼容的变更。
变更类别
下面列出的所有策略都按变更的级别进行分类
- 重大变更:需要提升主 SemVer 版本号的变更。
- 次要变更:仅需要提升次 SemVer 版本号的变更。
- 可能破坏性变更:某些项目可能认为是重大变更,而另一些项目可能认为是次要变更的变更。
“可能破坏性”类别涵盖了在更新期间可能会破坏,但不一定会导致破坏的变更。应该仔细考虑这些变更的影响。确切的性质将取决于变更和项目维护者的原则。
某些项目可能选择仅在次要变更时提升补丁版本号。 鼓励遵循 SemVer 规范,并且仅在补丁版本中应用错误修复。但是,错误修复可能需要标记为“次要变更”的 API 更改,并且不应影响兼容性。本指南不对每个“次要变更”应如何处理采取立场,因为次要变更和补丁变更之间的区别是取决于变更性质的约定。
有些变更被标记为“次要变更”,即使它们可能存在破坏构建的风险。 这是因为潜在风险极低,并且潜在的破坏代码不太可能以惯用的 Rust 编写,或者特别不鼓励使用。
本指南使用“重大”和“次要”术语,假设这与“1.0.0”或更高版本有关。 以“0.y.z”开头的初始开发版本可以将“y”中的更改视为主要版本,“z”视为次要版本。“0.0.z”版本始终是重大变更。 这是因为 Cargo 使用约定,即仅考虑最左边的非零组件中的更改是不兼容的。
- API 兼容性
- 项
- 类型
- 结构体
- 枚举
- 特性
- 实现
- 泛型
- 函数
- 属性
- 工具和环境兼容性
- 应用程序兼容性
API 兼容性
以下所有示例都包含三个部分:原始代码、修改后的代码以及可能出现在另一个项目中的代码用法示例。 在次要变更中,示例用法应能成功使用修改前后的版本构建。
重大:重命名/移动/删除任何公共项
缺少公开的项将导致该项的任何使用都无法编译。
// 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`
}
这包括添加任何类型的 cfg
属性,该属性可以根据条件编译更改哪些项或行为可用。
缓解策略
次要:添加新的公共项
添加新的公共项是次要变更。
// 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.
请注意,在某些极少数情况下,由于 glob 导入,这可能会成为破坏性变更。 例如,如果您添加一个新特性,并且一个项目使用了将该特性引入范围的 glob 导入,并且新特性引入了与它所实现的任何类型冲突的关联项,则可能由于歧义而导致编译时错误。 示例
// 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
}
这不被认为是重大变更,因为按照惯例,glob 导入是一种已知的前向兼容性风险。应避免从外部 crate glob 导入项。
重大:更改定义明确的类型的对齐方式、布局或大小
更改以前定义良好的类型的对齐方式、布局或大小是破坏性变更。
通常,使用默认表示形式的类型没有定义明确的对齐方式、布局或大小。编译器可以自由更改对齐方式、布局或大小,因此代码不应对其做出任何假设。
注意:即使类型未定义明确,如果外部 crate 对类型的对齐方式、布局或大小做出假设,则外部 crate 也可能会损坏。这不被认为是 SemVer 破坏性变更,因为不应做出这些假设。
以下是一些不属于破坏性变更的变更示例(假设本指南中没有其他规则被违反)
- 以遵循本指南中其他规则的方式(例如,使用
non_exhaustive
允许这些更改,或更改已经是私有的私有字段)添加、删除、重新排序或更改默认表示形式结构体、联合或枚举的字段。请参阅struct-add-private-field-when-public、struct-add-public-field-when-no-private、struct-private-fields-with-private、enum-fields-new。 - 如果枚举使用
non_exhaustive
,则向默认表示形式的枚举添加变体。 这可能会更改枚举的对齐方式或大小,但这些是未定义明确的。 请参阅enum-variant-new。 - 按照本指南中的其他规则(例如,使用
non_exhaustive
,或者在其他私有字段已存在时添加私有字段),添加、删除、重新排序或更改repr(C)
结构体、联合或枚举的私有字段。请参阅repr-c-private-change。 - 如果枚举使用
non_exhaustive
,则向repr(C)
枚举添加变体。请参阅repr-c-enum-variant-new。 - 向默认表示形式结构体、联合或枚举添加
repr(C)
。请参阅repr-c-add。 - 向枚举添加
repr(<int>)
原始表示形式。请参阅repr-int-enum-add。 - 向默认表示形式结构体或枚举添加
repr(transparent)
。请参阅repr-transparent-add。
可以使用repr
属性的类型,可以认为具有以某种方式定义的对齐方式和布局,代码可以对此做出一些假设,这些假设可能会因更改该类型而中断。
在某些情况下,具有 repr
属性的类型可能没有明确定义的对齐方式、布局或大小。在这些情况下,可以安全地更改类型,但应谨慎行事。例如,具有不以其他方式记录其对齐方式、布局或大小保证的私有字段的类型不能被外部 crate 依赖,因为公共 API 没有完全定义类型的对齐方式、布局或大小。
一个常见的示例是,具有私有字段的类型是明确定义的,即具有使用 repr(transparent)
的单个私有字段,并且文档的文字说明它是对泛型类型透明的。例如,请参阅UnsafeCell
。
以下是一些破坏性变更的示例
- 向结构体或联合添加
repr(packed)
。请参阅repr-packed-add。 - 向结构体、联合或枚举添加
repr(align)
。请参阅repr-align-add。 - 从结构体或联合删除
repr(packed)
。请参阅repr-packed-remove。 - 更改
repr(packed(N))
的值 N,如果这会改变对齐或布局。请参阅 repr-packed-n-change。 - 更改
repr(align(N))
的值 N,如果这会改变对齐。请参阅 repr-align-n-change。 - 从结构体、联合体或枚举中移除
repr(align)
。请参阅 repr-align-remove。 - 更改
repr(C)
类型的公共字段的顺序。请参阅 repr-c-shuffle。 - 从结构体、联合体或枚举中移除
repr(C)
。请参阅 repr-c-remove。 - 从枚举中移除
repr(<int>)
。请参阅 repr-int-enum-remove。 - 更改
repr(<int>)
枚举的原始表示。请参阅 repr-int-enum-change。 - 从结构体或枚举中移除
repr(transparent)
。请参阅 repr-transparent-remove。
次要:repr(C)
添加、移除或更改私有字段
通常,添加、删除或更改 repr(C)
结构体、联合体或枚举的私有字段是安全的,前提是它遵循本指南中的其他准则(请参阅 struct-add-private-field-when-public、 struct-add-public-field-when-no-private、struct-private-fields-with-private、enum-fields-new)。
例如,只有在已经有其他私有字段或它是 non_exhaustive
时,才能添加私有字段。如果存在私有字段,或者它是 non_exhaustive
,并且添加不会改变其他字段的布局,则可以添加公共字段。
但是,这可能会改变类型的大小和对齐方式。如果大小或对齐方式发生变化,则应谨慎。除非类型具有已记录的大小或对齐方式,否则代码不应假设具有私有字段或 non_exhaustive
的类型的大小或对齐方式。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[derive(Default)]
#[repr(C)]
pub struct Example {
pub f1: i32,
f2: i32, // a private field
}
///////////////////////////////////////////////////////////
// After
#[derive(Default)]
#[repr(C)]
pub struct Example {
pub f1: i32,
f2: i32,
f3: i32, // a new field
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
// NOTE: Users should not make assumptions about the size or alignment
// since they are not documented.
let f = updated_crate::Example::default();
}
次要:repr(C)
添加枚举变体
如果枚举使用 non_exhaustive
,则向 repr(C)
枚举添加变体通常是安全的。有关更多讨论,请参阅 enum-variant-new。
请注意,这可能是一个重大更改,因为它会改变类型的大小和对齐方式。有关类似的担忧,请参阅 repr-c-private-change。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C)]
#[non_exhaustive]
pub enum Example {
Variant1 { f1: i16 },
Variant2 { f1: i32 },
}
///////////////////////////////////////////////////////////
// After
#[repr(C)]
#[non_exhaustive]
pub enum Example {
Variant1 { f1: i16 },
Variant2 { f1: i32 },
Variant3 { f1: i64 }, // added
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
// NOTE: Users should not make assumptions about the size or alignment
// since they are not specified. For example, this raised the size from 8
// to 16 bytes.
let f = updated_crate::Example::Variant2 { f1: 123 };
}
次要:向默认表示添加 repr(C)
向具有默认表示的结构体、联合体或枚举添加 repr(C)
是安全的。 这是安全的,因为用户不应假设具有默认表示的类型的对齐方式、布局或大小。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Example {
pub f1: i32,
pub f2: i16,
}
///////////////////////////////////////////////////////////
// After
#[repr(C)] // added
pub struct Example {
pub f1: i32,
pub f2: i16,
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
let f = updated_crate::Example { f1: 123, f2: 456 };
}
次要:向枚举添加 repr(<int>)
向具有默认表示的枚举添加 repr(<int>)
原始表示是安全的。这是安全的,因为用户不应假设具有默认表示的枚举的对齐方式、布局或大小。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub enum E {
Variant1,
Variant2(i32),
Variant3 { f1: f64 },
}
///////////////////////////////////////////////////////////
// After
#[repr(i32)] // added
pub enum E {
Variant1,
Variant2(i32),
Variant3 { f1: f64 },
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
let x = updated_crate::E::Variant3 { f1: 1.23 };
}
次要:向默认表示结构体或枚举添加 repr(transparent)
向具有默认表示的结构体或枚举添加 repr(transparent)
是安全的。这是安全的,因为用户不应假设具有默认表示的结构体或枚举的对齐方式、布局或大小。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[derive(Default)]
pub struct Example<T>(T);
///////////////////////////////////////////////////////////
// After
#[derive(Default)]
#[repr(transparent)] // added
pub struct Example<T>(T);
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
let x = updated_crate::Example::<i32>::default();
}
主要:向结构体或联合体添加 repr(packed)
向结构体或联合体添加 repr(packed)
是一个重大更改。使类型为 repr(packed)
会进行更改,从而破坏代码,例如无法获取字段的引用,或导致不相交闭包捕获被截断。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Example {
pub f1: u8,
pub f2: u16,
}
///////////////////////////////////////////////////////////
// After
#[repr(packed)] // added
pub struct Example {
pub f1: u8,
pub f2: u16,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
fn main() {
let f = updated_crate::Example { f1: 1, f2: 2 };
let x = &f.f2; // Error: reference to packed field is unaligned
}
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Example(pub i32, pub i32);
///////////////////////////////////////////////////////////
// After
#[repr(packed)]
pub struct Example(pub i32, pub i32);
///////////////////////////////////////////////////////////
// Example usage that will break.
fn main() {
let mut f = updated_crate::Example(123, 456);
let c = || {
// Without repr(packed), the closure precisely captures `&f.0`.
// With repr(packed), the closure captures `&f` to avoid undefined behavior.
let a = f.0;
};
f.1 = 789; // Error: cannot assign to `f.1` because it is borrowed
c();
}
主要:向结构体、联合体或枚举添加 repr(align)
向结构体、联合体或枚举添加 repr(align)
是一个重大更改。使类型为 repr(align)
会破坏任何在 repr(packed)
类型中使用该类型的行为,因为不允许这种组合。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Aligned {
pub a: i32,
}
///////////////////////////////////////////////////////////
// After
#[repr(align(8))] // added
pub struct Aligned {
pub a: i32,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Aligned;
#[repr(packed)]
pub struct Packed { // Error: packed type cannot transitively contain a `#[repr(align)]` type
f1: Aligned,
}
fn main() {
let p = Packed {
f1: Aligned { a: 123 },
};
}
主要:从结构体或联合体中移除 repr(packed)
从结构体或联合体中移除 repr(packed)
是一个重大更改。这可能会改变外部 crate 所依赖的对齐方式或布局。
如果任何字段是公共的,那么移除 repr(packed)
可能会改变不相交闭包捕获的工作方式。在某些情况下,这可能会导致代码中断,类似于 edition 指南中概述的那些。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C, packed)]
pub struct Packed {
pub a: u8,
pub b: u16,
}
///////////////////////////////////////////////////////////
// After
#[repr(C)] // removed packed
pub struct Packed {
pub a: u8,
pub b: u16,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Packed;
fn main() {
let p = Packed { a: 1, b: 2 };
// Some assumption about the size of the type.
// Without `packed`, this fails since the size is 4.
const _: () = assert!(std::mem::size_of::<Packed>() == 3); // Error: evaluation of constant value failed
}
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C, packed)]
pub struct Packed {
pub a: *mut i32,
pub b: i32,
}
unsafe impl Send for Packed {}
///////////////////////////////////////////////////////////
// After
#[repr(C)] // removed packed
pub struct Packed {
pub a: *mut i32,
pub b: i32,
}
unsafe impl Send for Packed {}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Packed;
fn main() {
let mut x = 123;
let p = Packed {
a: &mut x as *mut i32,
b: 456,
};
// When the structure was packed, the closure captures `p` which is Send.
// When `packed` is removed, this ends up capturing `p.a` which is not Send.
std::thread::spawn(move || unsafe {
*(p.a) += 1; // Error: cannot be sent between threads safely
});
}
主要:更改 repr(packed(N))
的值 N,如果这会改变对齐或布局
如果更改 repr(packed(N))
的值 N 会改变对齐或布局,则这是一个重大更改。 这可能会改变外部 crate 所依赖的对齐方式或布局。
如果值 N
低于公共字段的对齐方式,那么这将破坏任何尝试获取该字段引用的代码。
请注意,对 N
的某些更改可能不会改变对齐方式或布局,例如,当当前值已经等于类型的自然对齐方式时增加它。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(packed(4))]
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// After
#[repr(packed(2))] // changed to 2
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Packed;
fn main() {
let p = Packed { a: 1, b: 2 };
let x = &p.b; // Error: reference to packed field is unaligned
}
主要:更改 repr(align(N))
的值 N,如果这会改变对齐
如果更改 repr(align(N))
的值 N
会改变对齐,则这是一个重大更改。 这可能会改变外部 crate 所依赖的对齐方式。
如果该类型不是类型布局中所讨论的定义良好的类型(例如,具有任何私有字段并具有未记录的对齐方式或布局),则此更改应该是安全的。
请注意,对 N
的某些更改可能不会改变对齐方式或布局,例如,当当前值已经等于或小于类型的自然对齐方式时减小它。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(align(8))]
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// After
#[repr(align(4))] // changed to 4
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Packed;
fn main() {
let p = Packed { a: 1, b: 2 };
// Some assumption about the size of the type.
// The alignment has changed from 8 to 4.
const _: () = assert!(std::mem::align_of::<Packed>() == 8); // Error: evaluation of constant value failed
}
主要:从结构体、联合体或枚举中移除 repr(align)
如果结构体、联合体或枚举的布局是定义良好的,则从中移除 repr(align)
是一个重大更改。这可能会改变外部 crate 所依赖的对齐方式或布局。
如果该类型不是类型布局中所讨论的定义良好的类型(例如,具有任何私有字段并具有未记录的对齐方式),则此更改应该是安全的。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C, align(8))]
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// After
#[repr(C)] // removed align
pub struct Packed {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Packed;
fn main() {
let p = Packed { a: 1, b: 2 };
// Some assumption about the size of the type.
// The alignment has changed from 8 to 4.
const _: () = assert!(std::mem::align_of::<Packed>() == 8); // Error: evaluation of constant value failed
}
主要:更改 repr(C)
类型的公共字段的顺序
更改 repr(C)
类型的公共字段的顺序是一个重大更改。 外部 crate 可能依赖于字段的特定顺序。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C)]
pub struct SpecificLayout {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// After
#[repr(C)]
pub struct SpecificLayout {
pub b: u32, // changed order
pub a: u8,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::SpecificLayout;
extern "C" {
// This C function is assuming a specific layout defined in a C header.
fn c_fn_get_b(x: &SpecificLayout) -> u32;
}
fn main() {
let p = SpecificLayout { a: 1, b: 2 };
unsafe { assert_eq!(c_fn_get_b(&p), 2) } // Error: value not equal to 2
}
mod cdep {
// This simulates what would normally be something included from a build script.
// This definition would be in a C header.
#[repr(C)]
pub struct SpecificLayout {
pub a: u8,
pub b: u32,
}
#[no_mangle]
pub fn c_fn_get_b(x: &SpecificLayout) -> u32 {
x.b
}
}
主要:从结构体、联合体或枚举中移除 repr(C)
从结构体、联合体或枚举中移除 repr(C)
是一个重大更改。 外部 crate 可能依赖于类型的特定布局。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(C)]
pub struct SpecificLayout {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// After
// removed repr(C)
pub struct SpecificLayout {
pub a: u8,
pub b: u32,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::SpecificLayout;
extern "C" {
// This C function is assuming a specific layout defined in a C header.
fn c_fn_get_b(x: &SpecificLayout) -> u32; // Error: is not FFI-safe
}
fn main() {
let p = SpecificLayout { a: 1, b: 2 };
unsafe { assert_eq!(c_fn_get_b(&p), 2) }
}
mod cdep {
// This simulates what would normally be something included from a build script.
// This definition would be in a C header.
#[repr(C)]
pub struct SpecificLayout {
pub a: u8,
pub b: u32,
}
#[no_mangle]
pub fn c_fn_get_b(x: &SpecificLayout) -> u32 {
x.b
}
}
主要:从枚举中移除 repr(<int>)
从枚举中移除 repr(<int>)
是一个重大更改。 外部 crate 可能假设判别式是特定大小的。 例如,枚举的 std::mem::transmute
可能会失败。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(u16)]
pub enum Example {
Variant1,
Variant2,
Variant3,
}
///////////////////////////////////////////////////////////
// After
// removed repr(u16)
pub enum Example {
Variant1,
Variant2,
Variant3,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
fn main() {
let e = updated_crate::Example::Variant2;
let i: u16 = unsafe { std::mem::transmute(e) }; // Error: cannot transmute between types of different sizes
}
主要:更改 repr(<int>)
枚举的原始表示
更改 repr(<int>)
枚举的原始表示是一个重大更改。 外部 crate 可能假设判别式是特定大小的。 例如,枚举的 std::mem::transmute
可能会失败。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(u16)]
pub enum Example {
Variant1,
Variant2,
Variant3,
}
///////////////////////////////////////////////////////////
// After
#[repr(u8)] // changed repr size
pub enum Example {
Variant1,
Variant2,
Variant3,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
fn main() {
let e = updated_crate::Example::Variant2;
let i: u16 = unsafe { std::mem::transmute(e) }; // Error: cannot transmute between types of different sizes
}
主要:从结构体或枚举中移除 repr(transparent)
从结构体或枚举中移除 repr(transparent)
是一个重大更改。 外部 crate 可能依赖于该类型具有透明字段的对齐方式、布局或大小。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[repr(transparent)]
pub struct Transparent<T>(T);
///////////////////////////////////////////////////////////
// After
// removed repr
pub struct Transparent<T>(T);
///////////////////////////////////////////////////////////
// Example usage that will break.
#![deny(improper_ctypes)]
use updated_crate::Transparent;
extern "C" {
fn c_fn() -> Transparent<f64>; // Error: is not FFI-safe
}
fn main() {}
重大:当所有当前字段都是公共的时,添加私有结构体字段
当向以前具有所有公共字段的结构体添加私有字段时,这将破坏任何尝试使用结构体字面量构造它的代码。
// 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: cannot construct `Foo`
}
缓解策略
- 不要向全公共字段结构体添加新字段。
- 首次引入结构体时,将其标记为
#[non_exhaustive]
,以防止用户使用结构体字面量语法,而是提供构造函数方法和/或 Default 实现。
重大:当不存在私有字段时,添加公共字段
当向具有所有公共字段的结构体添加公共字段时,这将破坏任何尝试使用结构体字面量构造它的代码。
// 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`
}
缓解策略
- 不要向全公共字段结构体添加新字段。
- 首次引入结构体时,将其标记为
#[non_exhaustive]
,以防止用户使用结构体字面量语法,而是提供构造函数方法和/或 Default 实现。
次要:当至少存在一个私有字段时,添加或删除私有字段
当结构体已经至少有一个私有字段时,添加或删除结构体的私有字段是安全的。
// 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();
}
这是安全的,因为现有代码不能使用结构体字面量来构造它,也不能穷尽地匹配其内容。
请注意,对于元组结构体,如果元组包含公共字段,并且添加或删除私有字段会更改任何公共字段的索引,则这是一个重大更改。
// 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 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();
}
这是安全的,因为现有代码不能使用结构体字面量来构造它,也不能匹配其内容。
重大:添加新的枚举变体(不使用 non_exhaustive
)
如果枚举未使用#[non_exhaustive]
属性,则添加新的枚举变体是一个重大更改。
// 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: `E::Variant2` not covered
E::Variant1 => {}
}
}
缓解策略
- 在引入枚举时,将其标记为
#[non_exhaustive]
,以强制用户使用通配符模式来捕获新的变体。
重大:向枚举变体添加新字段
向枚举变体添加新字段是一个重大更改,因为所有字段都是公共的,并且构造函数和匹配将无法编译。
// 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
}
}
缓解策略
- 在引入枚举时,将变体标记为
non_exhaustive
,以便在没有通配符的情况下无法构造或匹配它。pub enum E { #[non_exhaustive] Variant1{f1: i32} }
- 在引入枚举时,使用显式结构体作为值,您可以在其中控制字段的可见性。
pub struct Foo { f1: i32, f2: i32, } pub enum E { Variant1(Foo) }
重大:添加非默认特性项
向 trait 添加非默认项是一个重大更改。这将破坏 trait 的任何实现者。
// 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
缓解策略
- 始终为新的关联 trait 项提供默认实现或值。
- 在引入 trait 时,使用 密封 trait 技术,以防止 crate 外部的用户实现该 trait。
重大:任何对特性项签名的更改
对 trait 项签名进行任何更改都是破坏性变更。这可能会破坏 trait 的外部实现者。
// 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<V>(&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
}
缓解策略
- 引入带有默认实现的新项以覆盖新功能,而不是修改现有项。
- 在引入 trait 时,使用 密封 trait 技术,以防止 crate 外部的用户实现该 trait。
可能破坏性:添加默认的特性项
添加默认的 trait 项通常是安全的。但是,有时这可能会导致编译错误。例如,如果另一个 trait 中存在同名的方法,则可能会引入歧义。
// 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
}
请注意,对于固有实现上的名称冲突,这种歧义不存在,因为它们优先于 trait 项。
有关添加 trait 项时需要考虑的特殊情况,请参阅trait 对象安全性。
缓解策略
- 某些项目可能认为这种破坏是可以接受的,特别是当新项的名称不太可能与任何现有代码冲突时。请仔细选择名称以帮助避免这些冲突。此外,在更新依赖项时,可能可以接受要求下游用户添加消除歧义的语法来选择正确的函数。
重大:添加使特性不是对象安全的特性项
添加一个 trait 项,使 trait 变为不是对象安全的,这是一个破坏性变更。
// 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<dyn Trait> = Box::new(Foo); // Error: cannot be made into an object
}
反过来做是安全的(将非对象安全的 trait 变为安全的)。
重大:添加没有默认值的类型参数
向 trait 添加没有默认值的类型参数是一个破坏性变更。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub trait Trait {}
///////////////////////////////////////////////////////////
// After
pub trait Trait<T> {}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Trait;
struct Foo;
impl Trait for Foo {} // Error: missing generics
缓解策略
- 请参阅添加默认的 trait 类型参数。
次要:添加默认的特性类型参数
只要 trait 具有默认值,就可以安全地向其添加类型参数。外部实现者将使用默认值,而无需指定参数。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub trait Trait {}
///////////////////////////////////////////////////////////
// After
pub trait Trait<T = i32> {}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
use updated_crate::Trait;
struct Foo;
impl Trait for Foo {}
可能破坏性变更:添加任何固有项
通常,向实现添加固有项应该是安全的,因为固有项优先于 trait 项。但是,在某些情况下,如果该名称与具有不同签名的已实现 trait 项相同,则冲突可能会导致问题。
// 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 method takes 0 arguments but 1 argument was supplied
}
请注意,如果签名匹配,则不会出现编译时错误,但运行时行为可能会发生静默更改(因为它现在正在执行不同的函数)。
缓解策略
- 某些项目可能认为这种破坏是可以接受的,特别是当新项的名称不太可能与任何现有代码冲突时。请仔细选择名称以帮助避免这些冲突。此外,在更新依赖项时,可能可以接受要求下游用户添加消除歧义的语法来选择正确的函数。
重大:收紧泛型约束
收紧类型的泛型边界是一个破坏性变更,因为这可能会破坏期望较宽松边界的用户。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo<A> {
pub f1: A,
}
///////////////////////////////////////////////////////////
// After
pub struct Foo<A: Eq> {
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}: Eq` is not satisfied
}
次要:放松泛型约束
放宽类型的泛型边界是安全的,因为它只会扩展允许的范围。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo<A: Clone> {
pub f1: A,
}
///////////////////////////////////////////////////////////
// After
pub struct Foo<A> {
pub f1: A,
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
use updated_crate::Foo;
fn main() {
let s = Foo { f1: 123 };
}
次要:添加默认的类型参数
只要类型具有默认值,就可以安全地向其添加类型参数。所有现有引用都将使用默认值,而无需指定参数。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
#[derive(Default)]
pub struct Foo {}
///////////////////////////////////////////////////////////
// After
#[derive(Default)]
pub struct Foo<A = i32> {
f1: A,
}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
use updated_crate::Foo;
fn main() {
let s: Foo = Default::default();
}
次要:将类型泛化为使用泛型(具有相同的类型)
如果更改后的结果对于所有现有用例都具有相同的类型,则可以将结构体或枚举字段从具体类型更改为泛型类型参数。例如,允许以下更改
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo(pub u8);
///////////////////////////////////////////////////////////
// After
pub struct Foo<T = u8>(pub T);
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
use updated_crate::Foo;
fn main() {
let s: Foo = Foo(123);
}
因为 `Foo` 的现有用法是 `Foo
重大:将类型泛化为使用泛型(可能具有不同的类型)
如果类型可以更改,则将结构体或枚举字段从具体类型更改为泛型类型参数可能会导致破坏。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo<T = u8>(pub T, pub u8);
///////////////////////////////////////////////////////////
// After
pub struct Foo<T = u8>(pub T, pub T);
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::Foo;
fn main() {
let s: Foo<f32> = Foo(3.14, 123); // Error: mismatched types
}
次要:将泛型类型更改为更通用的类型
将泛型类型更改为更通用的类型是安全的。例如,以下操作添加了一个默认为原始类型的泛型参数,这是安全的,因为所有现有用户都将对两个字段使用相同的类型,而无需指定默认参数。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo<T>(pub T, pub T);
///////////////////////////////////////////////////////////
// After
pub struct Foo<T, U = T>(pub T, pub U);
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
use updated_crate::Foo;
fn main() {
let s: Foo<f32> = Foo(1.0, 2.0);
}
重大:添加/删除函数参数
更改函数的元数是一个破坏性变更。
// 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
}
缓解策略
- 引入具有新签名的新函数,并且可能弃用旧函数。
- 引入接受结构体参数的函数,其中结构体使用构建器模式构建。这允许将来向结构体添加新字段。
可能破坏性:引入新的函数类型参数
通常,添加非默认类型参数是安全的,但在某些情况下,它可能是一个破坏性变更
// Breaking change example
///////////////////////////////////////////////////////////
// Before
pub fn foo<T>() {}
///////////////////////////////////////////////////////////
// After
pub fn foo<T, U>() {}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::foo;
fn main() {
foo::<u8>(); // Error: function takes 2 generic arguments but 1 generic argument was supplied
}
但是,这种显式调用很少见(并且通常可以用其他方式编写),因此这种破坏通常是可以接受的。应该考虑使用显式类型参数调用相关函数的可能性有多大。
次要:将函数泛化为使用泛型(支持原始类型)
只要可以实例化为原始类型,就可以泛化函数的参数类型或其返回值以使用泛型,包括引入新的类型参数。例如,允许以下更改
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub fn foo(x: u8) -> u8 {
x
}
pub fn bar<T: Iterator<Item = u8>>(t: T) {}
///////////////////////////////////////////////////////////
// After
use std::ops::Add;
pub fn foo<T: Add>(x: T) -> T {
x
}
pub fn bar<T: IntoIterator<Item = u8>>(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());
}
因为所有现有用法都是新签名的实例化。
可能有点令人惊讶的是,泛化也适用于 trait 对象,因为每个 trait 都实现了自身
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub trait Trait {}
pub fn foo(t: &dyn Trait) {}
///////////////////////////////////////////////////////////
// After
pub trait Trait {}
pub fn foo<T: Trait + ?Sized>(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);
}
(`?Sized` 的使用至关重要;否则您无法恢复原始签名。)
以这种方式引入泛型可能会导致类型推断失败。这些通常很少见,对于某些项目来说可能是可以接受的破坏,因为这可以通过额外的类型注释来修复。
// Breaking change example
///////////////////////////////////////////////////////////
// Before
pub fn foo() -> i32 {
0
}
///////////////////////////////////////////////////////////
// After
pub fn foo<T: Default>() -> T {
Default::default()
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::foo;
fn main() {
let x = foo(); // Error: type annotations needed
}
重大:将函数泛化为使用具有类型不匹配的泛型
如果泛型类型约束或更改了先前允许的类型,则更改函数参数或返回类型是一个破坏性变更。例如,以下操作添加了一个现有代码可能不满足的泛型约束
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub fn foo(x: Vec<u8>) {}
///////////////////////////////////////////////////////////
// After
pub fn foo<T: Copy + IntoIterator<Item = u8>>(x: T) {}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::foo;
fn main() {
foo(vec![1, 2, 3]); // Error: `Copy` is not implemented for `Vec<u8>`
}
次要:使 unsafe
函数安全
以前是 `unsafe` 的函数可以安全化,而不会破坏代码。
但是请注意,它可能会导致`unused_unsafe` lint 触发,如下例所示,这将导致指定了 `#![deny(warnings)]` 的本地 crate 停止编译。根据引入新的 lint,允许更新引入新的警告。
反过来(使安全函数变为 `unsafe`)是一个破坏性变更。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub unsafe fn foo() {}
///////////////////////////////////////////////////////////
// After
pub fn foo() {}
///////////////////////////////////////////////////////////
// Example use of the library that will trigger a lint.
use updated_crate::foo;
unsafe fn bar(f: unsafe fn()) {
f()
}
fn main() {
unsafe { foo() }; // The `unused_unsafe` lint will trigger here
unsafe { bar(foo) };
}
使以前 `unsafe` 的结构体/枚举上的关联函数或方法安全化也是一个较小的更改,而对于 trait 上的关联函数则并非如此(请参阅对 trait 项签名进行任何更改)。
重大:从支持 no_std
切换到需要 std
如果您的库专门支持 `no_std` 环境,则发布需要 `std` 的新版本是一个破坏性变更。
// 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();
}
缓解策略
- 避免这种情况的常见习惯用法是包含一个 `std` Cargo 功能,该功能可以选择启用 `std` 支持,并且当该功能关闭时,该库可以在 `no_std` 环境中使用。
重大:向现有的枚举、变体或没有私有字段的结构体添加 non_exhaustive
将项标记为 `#[non_exhaustive]` 会更改它们在定义它们的 crate 外部的使用方式
- 非穷举结构体和枚举变体不能使用结构体字面量语法构造,包括函数式更新语法。
- 对非穷举结构体的模式匹配需要 `..`,并且对枚举的匹配不计入穷举性。
- 不允许使用 `as` 将枚举变体强制转换为它们的判别式。
具有私有字段的结构体不能使用结构体字面量语法构造,无论是否使用`#[non_exhaustive]`。向这样的结构体添加`#[non_exhaustive]`不是一个破坏性变更。
// MAJOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub struct Foo {
pub bar: usize,
}
pub enum Bar {
X,
Y(usize),
Z { a: usize },
}
pub enum Quux {
Var,
}
///////////////////////////////////////////////////////////
// After
#[non_exhaustive]
pub struct Foo {
pub bar: usize,
}
pub enum Bar {
#[non_exhaustive]
X,
#[non_exhaustive]
Y(usize),
#[non_exhaustive]
Z { a: usize },
}
#[non_exhaustive]
pub enum Quux {
Var,
}
///////////////////////////////////////////////////////////
// Example usage that will break.
use updated_crate::{Bar, Foo, Quux};
fn main() {
let foo = Foo { bar: 0 }; // Error: cannot create non-exhaustive struct using struct expression
let bar_x = Bar::X; // Error: unit variant `X` is private
let bar_y = Bar::Y(0); // Error: tuple variant `Y` is private
let bar_z = Bar::Z { a: 0 }; // Error: cannot create non-exhaustive variant using struct expression
let q = Quux::Var;
match q {
Quux::Var => 0,
// Error: non-exhaustive patterns: `_` not covered
};
}
缓解策略
- 首次引入结构体、枚举和枚举变体时,请将它们标记为`#[non_exhaustive]`,而不是稍后添加`#[non_exhaustive]`。
工具和环境兼容性
可能破坏性:更改所需的最低 Rust 版本
在新的 Rust 版本中使用新功能可能会破坏使用旧版本 Rust 的项目。这还包括在新版本的 Cargo 中使用新功能,以及在以前在稳定版上工作的 crate 中要求使用仅在 nightly 上可用的功能。
通常建议将其视为较小的更改,而不是主要更改,原因有很多。更新到较新版本的 Rust 通常相对容易。Rust 也有快速的 6 周发布周期,并且某些项目会在发布窗口(例如当前稳定版本加上 N 个以前的版本)内提供兼容性。请记住,一些大型项目可能无法快速更新其 Rust 工具链。
缓解策略
- 使用Cargo 功能使新功能成为可选的。
- 为旧版本提供较长的支持窗口。
- 如果可能,复制新的标准库项的源代码,以便您可以继续使用旧版本,但可以利用新功能。
- 提供一个单独的旧版本次要发行版分支,该分支可以接收重要错误修复的向后移植。
- 请留意 `[cfg(version(..))]` 和 `#[cfg(accessible(..))]` 功能,它们为新功能提供了选择加入机制。这些功能目前不稳定,仅在 nightly 通道中可用。
可能破坏性:更改平台和环境要求
库对其运行的环境有非常广泛的假设,例如主机平台、操作系统版本、可用服务、文件系统支持等。如果您发布的新版本限制了以前支持的内容,例如需要较新版本的操作系统,则它可能是一个破坏性变更。这些更改可能很难跟踪,因为您可能并不总是知道更改是否在未自动测试的环境中发生中断。
某些项目可能认为这种破坏是可以接受的,特别是当大多数用户不太可能发生中断时,或者该项目没有资源来支持所有环境。另一个值得注意的情况是,当供应商停止对某些硬件或操作系统的支持时,项目可能认为停止支持也是合理的。
缓解策略
- 记录您专门支持的平台和环境。
- 在 CI 中的各种环境中测试您的代码。
次要:引入新的 lint
对库的一些更改可能会导致该库的用户触发新的 lint。通常应将其视为兼容的更改。
// MINOR CHANGE
///////////////////////////////////////////////////////////
// Before
pub fn foo() {}
///////////////////////////////////////////////////////////
// After
#[deprecated]
pub fn foo() {}
///////////////////////////////////////////////////////////
// Example use of the library that will safely work.
fn main() {
updated_crate::foo(); // Warning: use of deprecated function
}
请注意,如果他们明确拒绝了警告,并且更新后的 crate 是直接依赖项,则这可能会在技术上导致项目失败。拒绝警告应谨慎进行,并理解随着时间的推移可能会引入新的 lint。但是,库作者应谨慎引入新的警告,并可能需要考虑对其用户造成的潜在影响。
以下 lint 是在更新依赖项时可能引入的 lint 的示例
- `deprecated` - 当依赖项向您正在使用的项添加`#[deprecated]` 属性时引入。
- `unused_must_use` - 当依赖项向您未使用结果的项添加`#[must_use]` 属性时引入。
- `unused_unsafe` - 当依赖项从函数中删除 `unsafe` 限定符,并且该函数是 unsafe 块中调用的唯一 unsafe 函数时引入。
此外,将 `rustc` 更新到新版本可能会引入新的 lint。
引入新的 lint 的传递依赖项通常不应导致失败,因为 Cargo 使用`--cap-lints`来禁止依赖项中的所有 lint。
缓解策略
- 如果您在拒绝警告的情况下进行构建,请理解您可能需要在更新依赖项时处理解决新警告的问题。如果使用 RUSTFLAGS 传递 `-Dwarnings`,还请添加 `-A` 标志以允许可能导致问题的 lint,例如 `-Adeprecated`。
- 在功能背后引入弃用。例如 `#[cfg_attr(feature = "deprecated", deprecated="use bar instead")]`。然后,当您计划在未来的 SemVer 破坏性变更中删除某个项时,您可以与您的用户沟通,他们应在更新以删除弃用项之前启用 `deprecated` 功能。这允许用户选择何时响应弃用,而无需立即响应它们。缺点是,很难与用户沟通,他们需要采取这些手动步骤来为重大更新做准备。
Cargo
次要:添加新的 Cargo 功能
通常,添加新的Cargo 功能是安全的。如果该功能引入了导致破坏性变更的新更改,则可能会给具有更严格的向后兼容性需求的项目带来困难。在这种情况下,请避免将该功能添加到“默认”列表中,并可能记录启用该功能的后果。
# MINOR CHANGE
###########################################################
# Before
[features]
# ..empty
###########################################################
# After
[features]
std = []
重大:删除 Cargo 功能
移除 Cargo features 通常是一个破坏性更改。 这会导致任何启用了该功能的项目出错。
# MAJOR CHANGE
###########################################################
# Before
[features]
logging = []
###########################################################
# After
[dependencies]
# ..logging removed
缓解策略
- 清楚地记录你的 feature。 如果存在内部或实验性的 feature,请将其标记出来,以便用户了解该 feature 的状态。
- 将旧的 feature 保留在
Cargo.toml
中,但在其他方面移除其功能。 记录该 feature 已被弃用,并在未来的主要 SemVer 版本中将其删除。
重大:如果更改功能或公共项,则从功能列表中删除功能
如果从另一个 feature 中移除一个 feature,这可能会破坏现有用户,如果他们期望通过该 feature 来使用该功能。
# Breaking change example
###########################################################
# Before
[features]
default = ["std"]
std = []
###########################################################
# After
[features]
default = [] # This may cause packages to fail if they are expecting std to be enabled.
std = []
可能破坏性:删除可选依赖项
移除一个 可选依赖 可能会破坏使用你库的项目,因为另一个项目可能通过 Cargo features 启用了该依赖。
当存在可选依赖时,cargo 会隐式定义一个同名的 feature,以提供一种启用依赖并检查何时启用的机制。 可以通过在 [features]
表中使用 dep:
语法来避免此问题,该语法会禁用此隐式 feature。 使用 dep:
可以将可选依赖的存在隐藏在语义上更相关的名称下,从而可以更安全地修改这些名称。
# Breaking change example
###########################################################
# Before
[dependencies]
curl = { version = "0.4.31", optional = true }
###########################################################
# After
[dependencies]
# ..curl removed
# MINOR CHANGE
#
# This example shows how to avoid breaking changes with optional dependencies.
###########################################################
# Before
[dependencies]
curl = { version = "0.4.31", optional = true }
[features]
networking = ["dep:curl"]
###########################################################
# After
[dependencies]
# Here, one optional dependency was replaced with another.
hyper = { version = "0.14.27", optional = true }
[features]
networking = ["dep:hyper"]
缓解策略
- 在
[features]
表中使用dep:
语法,以避免首先暴露可选依赖。 有关更多信息,请参见 可选依赖。 - 清楚地记录你的 feature。 如果可选依赖项未包含在已记录的 feature 列表中,那么你可以认为更改未记录的条目是安全的。
- 保留可选依赖项,只是不要在你的库中使用它。
- 将可选依赖项替换为不执行任何操作的 Cargo feature,并记录该 feature 已被弃用。
- 使用启用可选依赖的高级 feature,并将这些 feature 作为启用扩展功能的首选方式记录下来。 例如,如果你的库对诸如“网络”之类的东西具有可选支持,请创建一个通用 feature 名称“networking”,该名称启用实现“networking”所需的可选依赖项。 然后记录“networking” feature。
次要:更改依赖项功能
通常更改依赖项的 feature 是安全的,只要该 feature 不会引入破坏性更改即可。
# MINOR CHANGE
###########################################################
# Before
[dependencies]
rand = { version = "0.7.3", features = ["small_rng"] }
###########################################################
# After
[dependencies]
rand = "0.7.3"
次要:添加依赖项
通常添加新的依赖项是安全的,只要新的依赖项不引入导致破坏性更改的新要求即可。 例如,在一个之前在 stable 版本上工作的项目中添加一个需要 nightly 的新依赖项是一个重大更改。
# MINOR CHANGE
###########################################################
# Before
[dependencies]
# ..empty
###########################################################
# After
[dependencies]
log = "0.4.11"
应用程序兼容性
Cargo 项目还可能包含可执行二进制文件,这些二进制文件具有自己的接口(例如 CLI 接口、操作系统级交互等)。由于这些是 Cargo 包的一部分,它们通常使用并共享与该包相同的版本。 你需要决定是否以及如何在你对应用程序所做的更改中与你的用户使用 SemVer 合约。 应用程序的潜在破坏性更改和兼容性更改太多,无法一一列出,因此我们鼓励你使用 SemVer 规范的精神来指导你关于如何将版本控制应用于你的应用程序的决策,或者至少记录你的承诺是什么。