Despite the fact that Rust has absorbed many good modern programming ideas, it looks like one very basic feature is not presented.
The modern (pseudo-)functional code is based on a large number of classes of the following kind:
pub struct NamedTuple {
a: i8,
b: char,
}
impl NamedTuple {
fn new(a: i8, b: char) -> NamedTuple {
NamedTuple { a: a, b: b }
}
fn a(&self) -> i8 {
self.a
}
fn b(&self) -> char {
self.b
}
}
As you can see, there is a lot of boilerplate code here. Is there really no way to describe such types compactly, without a boilerplate code?
When you have boilerplate, think macros:
macro_rules! ro {
(
pub struct $name:ident {
$($fname:ident : $ftype:ty),*
}
) => {
pub struct $name {
$($fname : $ftype),*
}
impl $name {
fn new($($fname : $ftype),*) -> $name {
$name { $($fname),* }
}
$(fn $fname(&self) -> $ftype {
self.$fname
})*
}
}
}
ro!(pub struct NamedTuple {
a: i8,
b: char
});
fn main() {
let n = NamedTuple::new(42, 'c');
println!("{}", n.a());
println!("{}", n.b());
}
This is a basic macro and could be extended to handle specifying visibility as well as attributes / documentation on the struct and the fields.
I'd challenge that you have as much boilerplate as you think you do. For example, you only show Copy
types. As soon as you add a String
or a Vec
to your structs, this will fall apart and you need to either return a reference or take self
.
Editorially, I don't think this is good or idiomatic Rust code. If you have a value type where people need to dig into it, just make the fields public:
pub struct NamedTuple {
pub a: i8,
pub b: char,
}
fn main() {
let n = NamedTuple { a: 42, b: 'c' };
println!("{}", n.a);
println!("{}", n.b);
}
Existing Rust features prevent most of the problems that getter methods attempt to solve in the first place.
Variable binding-based mutability
n.a = 43;
error[E0594]: cannot assign to field `n.a` of immutable binding
The rules of references
struct Something;
impl Something {
fn value(&self) -> &NamedTuple { /* ... */ }
}
fn main() {
let s = Something;
let n = s.value();
n.a = 43;
}
error[E0594]: cannot assign to field `n.a` of immutable binding
If you've transferred ownership of a value type to someone else, who cares if they change it?
Note that I'm making a distinction about value types as described by Growing Object-Oriented Software Guided by Tests, which they distinguish from objects. Objects should not have exposed internals.
Rust doesn't offer a built-in way to generate getters. However, there are multiple Rust features that can be used to tackle boilerplate code! The two most important ones for your question:
#[derive(...)]
attributemacro_rules!
(see @Shepmaster's answer on how to use those to solve your problem)I think the best way to avoid boilerplate code like this is to use custom derives. This allows you to add a #[derive(...)]
attribute to your type and generate these getters at compile time.
There is already a crate that offers exactly this: derive-getters
. It works like this:
#[derive(Getters)]
pub struct NamedTuple {
a: i8,
b: char,
}
There is also getset
, but it has two problems: getset
should have derive
in its crate name, but more importantly, it encourages the "getters & setters for everything" anti pattern by offering to also generate setters which don't perform any checks.
Finally, you might want to consider rethinking your approach to programming in Rust. Honestly, from my experience, "getter boilerplate" is hardly a problem. Sure, sometimes you need to write getters, but not "a large number" of them.
Mutability is also not unidiomatic in Rust. Rust is a multi paradigm language, supporting many styles of programming. Idiomatic Rust uses the most useful paradigm for each situation. Completely avoiding mutation might not be the best way to program in Rust. Furthermore, avoiding mutability is not only achieved by providing getters for your fields -- binding and reference mutability is far more important!
So, use read-only access to fields where it's useful, but not everywhere.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With