G.TYP.SCT.01 对外导出的公开的 Struct,宜添加#[non_exhaustive]属性

【级别】 建议

【描述】

作为对外公开的 结构体,为了保持稳定性,应该使用 #[non_exhaustive]属性,避免因为将来结构体字段发生变化而影响到下游的使用。主要涉及命名结构体和元组结构体。

【反例】

#[non_exhaustive] 属性稳定之前,社区内还有一种约定俗成的写法来达到防止下游自定义枚举方法。通过 manual_non_exhaustive 可以监控这类写法。


#![allow(unused)]
#![warn(clippy::exhaustive_structs)]

fn main() {
struct S {
    pub a: i32,
    pub b: i32,
    _priv: (),  // 不符合:这里用 下划线作为前缀定义的字段,作为私有字段,不对外公开
}

// 用户无法自定义实现该结构体的方法。
}

【正例】


#![allow(unused)]
#![warn(clippy::exhaustive_structs)]

fn main() {
// 符合
#[non_exhaustive]
struct Foo {
    bar: u8,
    baz: String,
}
}

【例外】

也有例外情况!

从语义角度看,#[non_exhaustive] 只是代表未穷尽的字段或枚举变体,是为了表达“未来可能有变化”这种语义。

但是当要表达 “这个结构体不允许对方实例化” 的语义时,通过自定义的 _priv字段就可以更好地表达这个语义。

而使用 #[non_exhaustive] 虽然也能达到 “不允许对方实例化的目的”,但是在代码可读性层面,却无法表达出这个语义。


#![allow(unused)]
fn main() {
// From: https://github.com/tokio-rs/tokio/blob/master/tokio-util/src/codec/framed.rs

#[allow(clippy::manual_non_exhaustive)]
pub struct FramedParts<T, U> {
    pub io: T,
    pub codec: U,
    pub read_buf: BytesMut,
    pub write_buf: BytesMut,
    
    /// This private field allows us to add additional fields in the future in a
    /// backwards compatible way.
    _priv: (),
}

impl<T, U> FramedParts<T, U> {
    /// Create a new, default, `FramedParts`
    pub fn new<I>(io: T, codec: U) -> FramedParts<T, U>
    where
        U: Encoder<I>,
    {
        FramedParts {
            io,
            codec,
            read_buf: BytesMut::new(),
            write_buf: BytesMut::new(),
            _priv: (),
        }
    }
}
}

【Lint 检测】

lint nameClippy 可检测Rustc 可检测Lint Grouplevel
exhaustive_structsyesnorestrictionallow
manual_non_exhaustiveyesnostylewarn