语义化版本兼容性

本章详细介绍了哪些情况通常被认为是包新版本的兼容或破坏性语义化版本更改。有关语义化版本的详细信息,以及 Cargo 如何使用它来确保库的兼容性,请参阅语义化版本兼容性部分。

这些只是指南,不一定是所有项目都必须遵守的硬性规定。更改类别部分详细介绍了本指南如何对更改的级别和严重程度进行分类。本指南的大部分内容都集中在会导致cargorustc无法构建以前可以正常工作的代码的更改上。几乎所有更改都存在负面影响运行时行为的风险,对于这些情况,通常由项目维护者判断它是否是语义化版本不兼容的更改。

更改类别

以下列出的所有策略都按更改级别分类

  • 主要更改:需要进行主要语义化版本升级的更改。
  • 次要更改:只需要进行次要语义化版本升级的更改。
  • 可能破坏性的更改:某些项目可能认为是主要更改,而其他项目认为是次要更改的更改。

“可能破坏性的更改”类别涵盖了在更新期间可能会导致破坏的更改,但不一定会导致破坏。应仔细考虑这些更改的影响。确切的性质将取决于更改和项目维护者的原则。

一些项目可能会选择仅在次要更改时升级补丁号。建议遵循语义化版本规范,并且仅在补丁版本中应用错误修复。但是,错误修复可能需要进行标记为“次要更改”的 API 更改,并且不应影响兼容性。本指南不对如何处理每个单独的“次要更改”发表意见,因为次要更改和补丁更改之间的区别是取决于更改性质的约定。

某些更改被标记为“次要更改”,即使它们有可能破坏构建。这是针对可能性极低的情况,并且可能破坏性的代码不太可能用惯用的 Rust 编写,或者明确建议不要使用。

本指南使用术语“主要”和“次要”假设这与“1.0.0”或更高版本的版本有关。以“0.y.z”开头的初始开发版本可以将“y”中的更改视为主要版本,将“z”中的更改视为次要版本。“0.0.z”版本始终是主要更改。这是因为 Cargo 使用的约定是,只有最左侧非零组件中的更改才被视为不兼容。

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 属性,该属性可以根据 条件编译 更改可用的项或行为。

缓解策略

  • 将要删除的项标记为 已弃用,然后在以后的 SemVer 不兼容版本中将其删除。
  • 将重命名的项标记为 已弃用,并使用 pub use 项重新导出到旧名称。

次要更改:添加新的公共项

添加新的公共 是一个次要更改。

// 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.

请注意,在极少数情况下,由于全局导入,这可能是一个**重大更改**。例如,如果您添加了一个新的特征,并且一个项目使用了将该特征引入作用域的全局导入,并且新特征引入了一个与其所实现的任何类型冲突的关联项,则这可能会由于歧义而导致编译时错误。示例

// 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
}

这不被认为是重大更改,因为传统上全局导入是一个已知的向前兼容性风险。应避免全局导入来自外部 crate 的项。

主要更改:更改定义良好的类型的对齐方式、布局或大小

更改先前定义良好的类型的对齐方式、布局或大小是一个重大更改。

通常,使用 默认表示形式 的类型没有定义良好的对齐方式、布局或大小。编译器可以自由更改对齐方式、布局或大小,因此代码不应对其做任何假设。

**注意**:如果外部 crate 对类型的对齐方式、布局或大小做出假设,即使它没有明确定义,也可能会导致外部 crate 崩溃。这并不被认为是 SemVer 不兼容更改,因为不应该做出这些假设。

以下是一些非重大更改的示例(假设本指南中的其他规则均未违反)

可以使用 repr 属性 的类型可以说具有以某种方式定义的对齐方式和布局,代码可能会对此做出一些假设,而更改该类型可能会破坏这些假设。

在某些情况下,具有 repr 属性的类型可能没有明确定义的对齐方式、布局或大小。在这些情况下,更改类型可能是安全的,但应谨慎行事。例如,具有私有字段且未在其文档中说明其对齐方式、布局或大小保证的类型不能被外部 crate 依赖,因为公共 API 没有完全定义该类型的对齐方式、布局或大小。

具有*私有*字段的类型定义良好的一个常见示例是具有单个私有字段(具有泛型类型)的类型,使用 repr(transparent),并且文档的说明讨论了它对泛型类型是透明的。例如,请参阅 UnsafeCell

以下是一些重大更改的示例

次要:repr(C) 添加、删除或更改私有字段

添加、删除或更改 repr(C) 结构体、联合或枚举的私有字段通常是安全的,假设它遵循本指南中的其他准则(请参阅 struct-add-private-field-when-publicstruct-add-public-field-when-no-privatestruct-private-fields-with-privateenum-fields-new)。

例如,只有在已经存在其他私有字段或它是 non_exhaustive 的情况下,才能添加私有字段。如果存在私有字段或它是 non_exhaustive,并且添加不会更改其他字段的布局,则可以添加公共字段。

但是,这可能会更改类型的 size 和 alignment。如果 size 或 alignment 发生变化,则应谨慎行事。代码不应假设具有私有字段或 non_exhaustive 的类型的 size 或 alignment,除非它具有记录的 size 或 alignment。

// 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

请注意,这可能是一个重大更改,因为它会更改类型的 size 和 alignment。有关类似问题,请参阅 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) 会导致破坏性变更。这可能会更改外部代码库所依赖的对齐方式或布局。

如果有任何字段是公开的,则移除 repr(packed) 可能会改变不相交闭包捕获的工作方式。在某些情况下,这会导致代码中断,类似于版本指南中概述的情况。

// 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 会导致对齐方式或布局发生变化,则会导致破坏性变更。这可能会更改外部代码库所依赖的对齐方式或布局。

如果值 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 会导致对齐方式发生变化,则会导致破坏性变更。这可能会更改外部代码库所依赖的对齐方式。

如果该类型未像类型布局中所述那样定义良好(例如,具有任何私有字段并且具有未记录的对齐方式或布局),则进行此更改应该是安全的。

请注意,对 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) 会导致破坏性变更。这可能会更改外部代码库所依赖的对齐方式或布局。

如果该类型未像类型布局中所述那样定义良好(例如,具有任何私有字段并且具有未记录的对齐方式),则进行此更改应该是安全的。

// 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) 类型的公共字段的顺序会导致破坏性变更。外部代码库可能依赖于字段的特定顺序。

// 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) 会导致破坏性变更。外部代码库可能依赖于该类型的特定布局。

// 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>) 会导致破坏性变更。外部代码库可能假设鉴别符具有特定大小。例如,对枚举进行 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>) 枚举的原始表示会导致破坏性变更。外部代码库可能假设鉴别符具有特定大小。例如,对枚举进行 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) 会导致破坏性变更。外部代码库可能依赖于该类型具有透明字段的对齐方式、布局或大小。

// 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 => {}
    }
}

缓解策略

主要更改:向枚举变体添加新字段

向枚举变体添加新字段会导致破坏性变更,因为所有字段都是公共的,并且构造函数和匹配将无法编译。

// 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)
    }

主要更改:添加非默认特征项

向特征添加非默认项会导致破坏性变更。这将破坏该特征的任何实现者。

// 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

缓解策略

  • 始终为新的关联特征项提供默认实现或值。
  • 在引入特征时,使用密封特征技术来防止代码库外部的用户实现该特征。

主要更改:对特征项签名的任何更改

对特征项签名进行任何更改都会导致破坏性变更。这可能会破坏该特征的外部实现者。

// 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
}

缓解策略

  • 引入具有默认实现的新项以涵盖新功能,而不是修改现有项。
  • 在引入特征时,使用密封特征技术来防止代码库外部的用户实现该特征。

可能破坏性的更改:添加默认特征项

添加默认特征项通常是安全的。但是,这有时会导致编译错误。例如,如果在另一个特征中存在同名方法,则这可能会引入歧义。

// 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
}

请注意,对于固有实现上的名称冲突,这种歧义*不*存在,因为它们优先于特征项。

有关添加特征项时要考虑的特殊情况,请参阅特征对象安全

缓解策略

  • 某些项目可能会认为这种破坏是可以接受的,特别是当新项名称不太可能与任何现有代码冲突时。请谨慎选择名称以帮助避免这些冲突。此外,在更新依赖项时,要求下游用户添加消除歧义语法以选择正确的函数可能是可以接受的。

主要更改:添加使特征非对象安全的特征项

添加特征项(如果这会使特征不再是对象安全的)会导致破坏性变更。

// 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
}

进行相反的操作(将非对象安全的特征转换为安全的特征)是安全的。

主要更改:添加没有默认值的类型参数

向特征添加没有默认值的类型参数会导致破坏性变更。

// 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

缓解策略

次要更改:添加默认特征类型参数

只要类型参数具有默认值,就可以安全地将其添加到特征中。外部实现者将使用默认值,而无需指定参数。

// 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 {}

可能破坏性的更改:添加任何固有项

通常,向实现添加固有项应该是安全的,因为固有项优先于特征项。但是,在某些情况下,如果名称与具有不同签名的已实现特征项相同,则冲突可能会导致问题。

// 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<u8> 的简写,这会产生相同的字段类型。

主要更改:将类型泛化为使用泛型(使用可能不同的类型)

如果类型可以更改,则将结构体或枚举字段从具体类型更改为泛型类型参数可能会导致中断。

// 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());
}

因为所有现有用法都是新签名的实例化。

也许有点令人惊讶的是,泛化也适用于特征对象,因为每个特征都实现了自身

// 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 的关联函数或方法安全也是一个次要更改,而特征上的关联函数则不是这样(请参阅对特征项签名的任何更改)。

主要更改:从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
    };
}

缓解策略

工具和环境兼容性

可能破坏性的更改:更改所需的最低 Rust 版本

在新版本的 Rust 中引入新特性的使用可能会破坏使用旧版本 Rust 的项目。这还包括在 Cargo 的新版本中使用新特性,以及在以前在稳定版上工作的 crate 中要求使用仅限夜间版的特性。

通常建议将其视为次要更改,而不是主要更改,原因多种多样。更新到较新版本的 Rust 通常相对容易。Rust 还有一个快速的 6 周发布周期,一些项目将在发布窗口内提供兼容性(例如当前的稳定版本加上之前的 N 个版本)。请记住,一些大型项目可能无法快速更新其 Rust 工具链。

缓解策略

  • 使用Cargo 特性使新特性成为可选的。
  • 为旧版本提供较长的支持窗口。
  • 如果可能,请复制新标准库项目的源代码,以便你可以继续使用旧版本,但利用新特性。
  • 提供一个单独的旧次要版本分支,可以接收重要错误修复的反向移植。
  • 密切关注[cfg(version(..))]#[cfg(accessible(..))] 特性,它们为新特性提供了一种选择加入机制。这些目前不稳定,并且仅在夜间频道中可用。

可能破坏性的更改:更改平台和环境要求

库对其运行环境做出了非常广泛的假设,例如主机平台、操作系统版本、可用服务、文件系统支持等。如果你发布了一个新版本,限制了以前支持的内容,例如需要更新版本的操作系统,那么这可能是一个破坏性更改。这些更改可能难以跟踪,因为你可能并不总是知道更改是否在未经自动测试的环境中破坏。

一些项目可能认为这是可以接受的破坏,特别是如果这种破坏对大多数用户来说不太可能发生,或者项目没有资源来支持所有环境。另一种值得注意的情况是,当供应商停止支持某些硬件或操作系统时,项目可能会认为停止支持也是合理的。

缓解策略

  • 记录你特别支持的平台和环境。
  • 在 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 的示例

此外,将 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 功能 通常是一个破坏性变更。这会导致任何启用了该功能的项目出错。

# MAJOR CHANGE

###########################################################
# Before
[features]
logging = []

###########################################################
# After
[dependencies]
# ..logging removed

缓解策略

  • 清晰地记录您的功能。如果存在内部或实验性功能,请将其标记为,以便用户了解功能的状态。
  • 将旧功能保留在 Cargo.toml 中,但删除其功能。记录该功能已弃用,并在未来的主要 SemVer 版本中将其删除。

主要更改:如果更改了功能或公共项,则从功能列表中删除功能

如果从另一个功能中删除一个功能,这可能会破坏现有用户,如果他们期望该功能可以通过该功能使用。

# 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 功能 启用了该依赖项。

当存在可选依赖项时,Cargo 会隐式定义一个同名功能,以提供一种机制来启用依赖项并在启用时进行检查。可以通过在 [features] 表中使用 dep: 语法来避免此问题,该语法禁用了此隐式功能。使用 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: 语法,以避免首先公开可选依赖项。有关更多信息,请参阅 可选依赖项
  • 清晰地记录您的功能。如果可选依赖项未包含在记录的功能列表中,则您可以决定将其视为可以安全地更改未记录的条目。
  • 保留可选依赖项,只是不要在您的库中使用它。
  • 用一个什么也不做的 Cargo 功能 替换可选依赖项,并记录它已被弃用。
  • 使用启用可选依赖项的高级功能,并将这些功能记录为启用扩展功能的首选方式。例如,如果您的库对“网络”之类的内容提供可选支持,请创建一个通用功能名称“网络”,以启用实现“网络”所需的可选依赖项。然后记录“网络”功能。

次要更改:更改依赖项功能

更改依赖项的功能通常是安全的,只要该功能不会引入破坏性变更。

# MINOR CHANGE

###########################################################
# Before
[dependencies]
rand = { version = "0.7.3", features = ["small_rng"] }


###########################################################
# After
[dependencies]
rand = "0.7.3"

次要:添加依赖项

添加新依赖项通常是安全的,只要新依赖项不会引入导致破坏性变更的新要求。例如,在一个以前在稳定版上工作的项目中添加一个需要 nightly 的新依赖项是一个重大变更。

# MINOR CHANGE

###########################################################
# Before
[dependencies]
# ..empty

###########################################################
# After
[dependencies]
log = "0.4.11"

应用程序兼容性

Cargo 项目还可以包含具有自己的接口(例如 CLI 接口、操作系统级交互等)的可执行二进制文件。由于这些是 Cargo 包的一部分,因此它们通常使用与包相同的版本并共享该版本。您需要决定是否以及如何对应用程序所做的更改采用 SemVer 协议。应用程序的潜在破坏性和兼容性更改太多,无法一一列举,因此建议您使用 SemVer 规范的精神来指导您决定如何将版本控制应用于您的应用程序,或者至少记录您的承诺是什么。