改进rust代码的35种具体方法-类型(二十二)-最小化可见度

上一篇文章 改进rust代码的35种具体方法-类型(二十一)-熟悉Cargo.toml版本使用


Rust允许将代码元素隐藏或暴露给代码库的其他部分。本项目探讨了为此提供的机制,并就应在何时何地使用它们提出建议。

可见性语法

Rust的基本可见性单位是模块。默认情况下,模块的项目(类型、方法、常量)是私有的,只能访问同一模块及其子模块中的代码。

需要更广泛可用的代码用pub关键字标记,使其向其他范围公开。对于大多数Rust语法功能,使功能pub不会自动公开内容——pub mod中的类型和功能不是公开的,也不是pub struct中的字段。然而,有几个例外情况是,将可见性应用于内容是有意义的:

  • 公开枚举会自动使类型的变体也公开(以及这些变体中可能存在的任何字段)。
  • 公开trait会自动公开该特征的方法。

因此,模块中的类型集合:

pub mod somemodule {
    // Making a `struct` public does not make its fields public.
    #[derive(Debug, Default)]
    pub struct AStruct {
        // By default fields are inaccessible.
        count: i32,
        // Fields have to be explicitly marked `pub` to be visible.
        pub name: String,
    }

    // Likewise, methods on the struct need individual `pub` markers.
    impl AStruct {
        // By default methods are inaccessible.
        fn canonical_name(&self) -> String {
            self.name.to_lowercase()
        }
        // Methods have to be explicitly marked `pub` to be visible.
        pub fn id(&self) -> String {
            format!("{}-{}", self.canonical_name(), self.count)
        }
    }

    // Making an `enum` public also makes all of its variants public.
    #[derive(Debug)]
    pub enum AnEnum {
        VariantOne,
        // Fields in variants are also made public.
        VariantTwo(u32),
        VariantThree { name: String, value: String },
    }

    // Making a `trait` public also makes all of its methods public.
    pub trait DoSomething {
        fn do_something(&self, arg: i32);
    }
}

允许访问pub的东西和前面提到的例外情况:

use somemodule::*;

let mut s = AStruct::default();
s.name = "Miles".to_string();
println!("s = {:?}, name='{}', id={}", s, s.name, s.id());

let e = AnEnum::VariantTwo(42);
println!("e = {e:?}");

#[derive(Default)]
pub struct DoesSomething;
impl DoSomething for DoesSomething {
    fn do_something(&self, _arg: i32) {}
}

let d = DoesSomething::default();
d.do_something(42);

但非pub的东西通常无法进入:

let mut s = AStruct::default();
s.name = "Miles".to_string();
println!("(inaccessible) s.count={}", s.count);
println!("(inaccessible) s.canonical_name()={}", s.canonical_name());
error[E0616]: field `count` of struct `somemodule::AStruct` is private
   --> src/main.rs:230:45
    |
230 |     println!("(inaccessible) s.count={}", s.count);
    |                                             ^^^^^ private field
error[E0624]: method `canonical_name` is private
   --> src/main.rs:231:56
    |
86  |         fn canonical_name(&self) -> String {
    |         ---------------------------------- private method defined here
...
231 |     println!("(inaccessible) s.canonical_name()={}", s.canonical_name());
    |                                         private method ^^^^^^^^^^^^^^
Some errors have detailed explanations: E0616, E0624.
For more information about an error, try `rustc --explain E0616`.

最常见的可见性标记是裸露的pub关键字,它使该项目对任何能够看到它所在的模块的东西可见。最后一个细节很重要:如果asomecratesomecrate::somemodule模块首先对其他代码不可见,那么它里面的任何pub仍然不可见。

然而,pub也有一些更具体的变体,允许限制可见性的范围。按有用性降序排列,如下所示:

  • pub(crate):在拥有的 crate 中可以随时访问。这对于 crate 内部的整个函数特别有用,这些函数不应该暴露给外部 crate 的用户。
  • pub(super):可以被当前模块的父模块及其子模块访问。这在具有深层模块结构的 crate 中,有时会有所帮助,可以有选择性地增加可见性。对于模块来说,这也是有效的可见性级别:一个简单的 mod mymodule 对其父模块或 crate 以及相应的子模块可见。
  • pub(in <path>):可访问<path>中的代码,该代码必须是当前模块某些祖先模块的描述。这偶尔对组织源代码很有用,因为它允许将功能子集移动到公共API中不一定可见的子模块中。例如,Rust标准库将所有迭代器适配器整合到一个内部std::iter::adapters子模块中,并具有以下内容:
  • pub(self):相当于pub(in self)这相当于不是pub。这样做的用途非常模糊,例如减少代码生成宏所需的特殊情况数量。

如果您有一个对模块私有但未在该模块(及其子模块)中使用的代码项,Rust编译器将警告您:

pub mod anothermodule {
    // Private function that is not used within its module.
    fn inaccessible_fn(x: i32) -> i32 {
        x + 3
    }
}

虽然警告表示代码在其拥有的模块中“从未使用过”,但在实践中,此警告通常表明代码不能从模块外部使用,因为可见性限制不允许:

warning: function `inaccessible_fn` is never used
  --> src/main.rs:56:8
   |
56 |     fn inaccessible_fn(x: i32) -> i32 {
   |        ^^^^^^^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

可见性语义

与如何提高知名度的问题不同,是何时这样做的问题。对此普遍接受的答案尽可能少,至少对于任何将来可能被使用和重复使用的代码。

提出这个建议的第一个原因是,可见性的变更很难撤销。一旦一个 crate 项是公共的,就不能再将其设置为私有,否则会破坏使用 crate 的任何代码,从而需要进行主要版本的升级。相反,事实并非如此:将私人项目移动为公共项目通常只需要一个次要版本颠簸,并且不会让板条箱用户不受影响——阅读Rust的API兼容性指南,并注意只有当有pub项目在起作用时,才会注意到有多少是相关的。

一个更重要但更微妙的偏爱隐私的理由是,它让你的选择保持开放。暴露的东西越多,未来需要固定的东西就越多(没有不兼容的变化)。如果您公开数据结构的内部实现细节,那么未来使用更高效算法的假定更改将成为突破性更改。如果您公开内部助手函数,一些外部代码将不可避免地取决于这些函数的确切细节。

当然,这只适用于可能具有多个用户且寿命较长的库代码。但没有什么比临时解决方案更永久的了,所以这是一个好习惯。

同样值得注意的是,这种限制可见性的建议绝不是这个项目或Rust所独有的:

  • Rust API指南包括以下建议:
  • 有效的Java,第3版,(Addison-Wesley Professional)有以下内容:
    • 项目15:尽量减少班级和成员的可访问性。
    • 项目16:在公共类中,使用访问器方法,而不是公共字段。
  • Scott Meyers(Addison-Wesley Professional)的有效C++第二版有以下内容:
    • 项目18:努力寻找完整和最小的类接口(我的斜体)。
    • 项目20:避免公共界面中的数据成员。

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值