Caveat
Please let me know if this has been suggested somewhere else as I could not find it via keyword searches in any of the internal forums or the RFC issues section. I'm 99.9% sure someone has suggested this before and there is some reason it's not supported but I cannot think of it or find the justification.
Proposal
Allow the user to better control the visibility of fields when defining a struct by allowing the users of a public struct containing private fields to instantiate the struct without the use of a constructor function. Currently if any of the fields in a struct are private the instantiating module cannot utilize the default construction method for structs, they must utilize constructors implemented specifically for that purpose.
This feature also would help when initializing structs that have many fields with at least one private field included, since constructors that accept many arguments make it easier to make a mistake and switch parameter locations on accident due to the fact they are positional in nature.
Users will be able to define a public struct containing private fields and this struct will be able to be instantiated without a constructor method in external modules, including separate crates. The below code for example, which does not compile currently, would now compile.
mod foo {
// Struct is public
pub struct Foo {
// Field is still private but during instantiation can be set
bar: usize
}
impl Foo {
// This RFC means that you don't have to define a constructor just
// because your struct has a private variable and needs to be
// instantiated in a separate module.
// Can access the private field the same way as before RFC
// implementation.
pub fn get_bar(&self) -> usize {
self.bar
}
}
}
fn main() {
let foo = foo::Foo {
bar: 0 // Compilation now succeeds using visibility change.
};
// // Still fails to compile as desired because `bar` is only public during
// // initialization.
// assert_eq!(foo.bar, 1)
// Succeeds as usual.
assert_eq!(foo.get_bar(), 0)
}
Alternatives
Wrapper Type Using #[repr(transparent)]
mod foo {
// Struct is public
pub struct FooWrapped {
// Field in wrapped struct is public
pub bar: usize
}
#[repr(transparent)]
pub struct Foo(FooWrapped /* private */);
impl Foo {
// Constructor is explicitly defined and simply takes in the wrapped
// struct.
pub fn new(foo: FooWrapped) -> Foo {
Self(foo)
}
// This is the only way I want to allow the user to access `bar`. I
// explicitly do not want to allow them to directly access struct
// fields.
pub fn get_bar(&self) -> usize {
self.0.bar
}
}
}
fn main() {
// Have to create a class to feed into the wrapper.
let foo_wrapped = foo::FooWrapped {
bar: 0
};
// Have to create a wrapper class rather than just using the default struct
// instantiation syntax.
let foo = foo::Foo::new(foo_wrapped);
// // This does still fail to compile, as desired.
// assert_eq!(foo.bar, 0)
// And this succeeds as desired.
assert_eq!(foo.get_bar(), 0)
}
Drawbacks
- Two structs have been created when one could suffice.
- Users have to import both structs. The first to create what amounts to a hash table, and the second to ingest the pseudo-hash-table and produce the desired struct.
- Users implementing methods for the wrapper have to specify
self.0
to access the data that has been wrapped.
Duplicate Struct and Copy in Constructor
Another alternative is to make two definitions of the same struct, one being a wrapper, where the wrapped struct has public fields and the wrapper has private fields.
mod foo {
// Struct is public
pub struct FooWrapped {
// Field in wrapped struct is public
pub bar: usize
}
pub struct Foo {
// Field in wrapper is private
bar: usize
}
impl Foo {
// Constructor is explicitly defined and simply takes in the wrapped
// struct.
pub fn new(foo: FooWrapped) -> Foo {
Self {
bar: foo.bar,
}
}
// This is the only way I want to allow the user to access `bar`. I
// explicitly do not want to allow them to directly access struct
// fields.
pub fn get_bar(&self) -> usize {
self.bar
}
}
}
fn main() {
// Have to create a class to feed into the wrapper.
let foo_wrapped = foo::FooWrapped {
bar: 0
};
// Have to create a wrapper class rather than just using the default struct
// instantiation syntax.
let foo = foo::Foo::new(foo_wrapped);
// // This does still fail to compile, as desired.
// assert_eq!(foo.bar, 0)
// And this succeeds as desired.
assert_eq!(foo.get_bar(), 0)
}
Drawbacks
- Two structs have been created when one could suffice.
- Users have to import both structs. The first to create what amounts to a hash table, and the second to ingest the pseudo-hash-table and produce the desired struct.
Make Fields pub
mod foo {
pub struct Foo {
// Field is public
pub bar: usize
}
impl Foo {
// Constructor doesn't need to be defined.
// This is the only way I want to allow the user to access `bar`. I
// explicitly do not want to allow them to directly access struct
// fields.
pub fn get_bar(&self) -> usize {
self.bar
}
}
}
fn main() {
// Instantiate the class.
let foo = foo::Foo {
bar: 0
};
// This does does *NOT* fail to compile resulting in the possibility of the
// implementing module declaring the instance mutable and mutating it's
// state improperly.
assert_eq!(foo.bar, 0)
// And this still succeeds.
assert_eq!(foo.get_bar(), 0)
}
Drawbacks
- Fields can be mutated (even by modules external to the crate the struct is defined in) without the knowledge of the rest of the struct, possibly resulting in disjointed state.
Make Fields pub(crate)
mod foo {
pub struct Foo {
// Field is public to all modules within the crate it is defined
pub(crate) bar: usize
}
impl Foo {
// Constructor doesn't need to be defined.
// This is the only way I want to allow the user to access `bar`. I
// explicitly do not want to allow them to directly access struct
// fields.
pub fn get_bar(&self) -> usize {
self.bar
}
}
}
fn main() {
// Instantiate the class.
let foo = foo::Foo {
bar: 0
};
// This does does *NOT* fail to compile resulting in the possibility of the
// implementing module declaring the instance mutable and mutating it's
// state improperly.
assert_eq!(foo.bar, 0)
// And this still succeeds.
assert_eq!(foo.get_bar(), 0)
}
Drawbacks
- Developers of the module that create the module are still permitted to access as fields directly. This should be caught at compile time like it is right now.
Drawbacks of Proposal
The "global hierarchy of namespaces" as mentioned will no longer be true. Even if it is only during instantiation/construction the struct's private fields will be visible to modules that previously would not be able to see what fields exist inside of a given struct.
Anything else anybody else can think of? I'm very new to RFCs and relatively new to Rust in general, all thoughts are welcome.
Questions To Get Answered
- What is a good name for this feature? I can't think of a short, unique,
fitting name.
init_fields
?
- Do any other languages use this feature?
- I believe C++ allows instantiating public structs with private properties but it's been a long time since I've used C++.
- Does this have any nasty visibility implications for existing code?
- It shouldn't since it only effects instantiating public structs that have private fields in external modules, which previously was a compilation error.
- Does this have any privacy safety implications for structs that implement
this method?
- It shouldn't since the fields are only "public" during instantiation of the struct and work exactly the same once instance is made.