3 releases
0.1.4 | Jan 13, 2024 |
---|---|
0.1.3 | Jan 13, 2024 |
0.1.2 | Jan 13, 2024 |
#1171 in Rust patterns
12KB
248 lines
internal
Private fields were a mistake.
What?
Ok, maybe that's a bit of an exaggeration.
Private fields aren't bad; they have a purpose:
- To allow library developers to make breaking changes to things without making major semver jumps.
- To prevent consumers from accidentally causing UB.
- To enable compiler optimizations.
But there's a better way.
The main problem with private fields is, well, it makes things private. It's too easy to lock potentially useful functionality away from your users.
A solution to that problem is internal fields, but Rust doesn't have that feature. This crate brings it to Rust via a proc macro and a feature flag.
How do internal fields work?
By default, internal fields can't be accessed, but they can be enabled and used when absolutely necessary.
It's a balanced solution to both ease of library development and freedom of library usage. It's easy to tell what could change, but nobody's limited or burdened. On top of all this, the compiler can still take advantage of performance improvements when internal fields aren't accessed.
The internal
crate does it "the Rust way" by exposing the fields only
when the "internal"
feature for the library is enabled. It also adds a
doc comment warning to the top of all internal fields to make it clear
when something is internal.
Usage
cargo add internal
To mark something as internal, use the internal
proc macro. It
effectively replaces private fields because those are useless when
internal fields exist.
The macro works recursively to mark everything under it that's private as
internal instead. So if you define #[internal] mod stuff {...}
, anything
inside and including stuff
that's private will become internal. If you
were to make stuff
public, it would always be public itself, but still
apply internal recursively.
your_lib
use internal::internal;
#[internal]
fn internal_fn(arg: InternalStruct) {
// ...
}
#[internal]
#[derive(Clone)]
struct InternalStruct {
field: PrivateThing
}
#[internal]
mod internal_mod {
pub struct PublicStruct {
internal_field: PublicThing
}
}
consumer
# Cargo.toml
your_lib = { features = ["internal"] }
// mod.rs
// If the `internal` feature is explicitly enabled,
// anything marked as internal will become public.
use your_lib::{internal_fn, InternalStruct, internal_mod};
internal_fn(InternalStruct {
field: ...
});
// Everything gets publicized recursively.
private_mod::PublicStruct {
internal_field: ...
}
Dependencies
~245–690KB
~16K SLoC