derive_more
derive_more copied to clipboard
Feature: derive Getter/Setter
The idea is to provide somewhat similar to getset crate, but with a more consistent design and being more clever about generics.
Motivation
I use "Parse, don't validate" approach a lot, which for Rust turns into the requirement almost never have pub
fields to ensure type invariants. While setters usually contain some logic which checks the invariants, 95% getters are just a noisy boilerplate. It would be nice to derive this boilerplate in a convenient way.
Proposed design
#[derive(Getter)]
pub struct Foo<A, B, C>
{
#[getter] // default is `ref` non-`pub` getter
private: A,
#[getter(ref(pub), ref_mut, owned(pub, suffix = "cloned"))]
public: B,
#[getter(owned(suffix = "copied"), ref_mut, prefix = "get")] // owned requires `Clone`
pub already_public: C, // if field `pub` then getter is `pub` too by default
}
expands into
impl<A, B, C> Foo<A, B, C> {
fn private(&self) -> &A {
&self.private
}
}
impl<A, B, C> Foo<A, B, C> {
pub fn public(&self) -> &B {
&self.public
}
}
impl<A, B, C> Foo<A, B, C> {
fn public_mut(&mut self) -> &mut B { // `ref_mut` by default adds suffix `_mut`
&mut self.public
}
}
impl<A, B: Clone, C> Foo<A, B, C> {
pub fn public_cloned(&self) -> B {
self.public.clone()
}
}
impl<A, B, C: Clone> Foo<A, B, C> {
pub fn get_already_public_copied(&self) -> C {
self.already_public.clone()
}
}
impl<A, B, C> Foo<A, B, C> {
pub fn get_already_public_mut(&mut self) -> &mut C {
&mut self.already_public
}
}
If this is something desirable, I'll submit the PR in a week or two.
Personally, please no getter/setter stuff, for various reasons:
https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html
https://blog.sentry.io/2018/04/05/you-cant-rust-that
https://marcus-biel.com/getters-and-setters-are-evil/
https://dev.to/scottshipp/avoid-getters-and-setters-whenever-possible-c8m
https://www.infoworld.com/article/2073723/why-getter-and-setter-methods-are-evil.html
@ronlobo pesonally, nobody pushes you to use them, thought. Getters/setters represent a good and idiomatic tool to encapsulate invariants behind a type and do not allow to accidentally break those invariants. However, they haven't meant to be used everywhere and for every field, nor they haven't meant to be used as the only way for encapsulation.
Misusing the tool doesn't make the tool bad, expecially in situations when it's used right.
Eventually, something is used if it is there, especially when more than one dev or an engineering team works on the same project. Invariants can also be captured in the constructor for an immutable data structure.
@ronlobo
Invariants can also be captured in the constructor for an immutable data structure.
Yup, that's what happens 95% of times. And then you usually need values of your fields, that is where getters come into play. Constructor + getters is the common way in Rust to ensure some invariants are met and won't be violated along. Setters, though, are really rare beasts.
I agree, however, personally I call getters accessors. Mutators (setters) are fine as long as they return a newly allocated data structure instead of modifying the existing one. This paradigm, immutable data structures, is heavily used in functional programming.
Originally I disliked the idea of adding more support for things that weren't traits in. After some more thinking I've come back to this though. For these types of common patterns I think it makes sense if derive_more
can remove the boilerplate code for you.
Input on the proposed design:
- What's the usecase for an
&mut
getter? Isn't this essentially a setter? Wouldn't you need to add extra code to these always? - For
owned
, lets make the default suffixcloned
? Since that's what it does. - Like the design proposes, let's only implement
Getter
for now, notSetter
.
Is this something you still care about @tyranron?
@JelteF yup, it just has a very low priority in my agenda regarding derive_more
. Someday, I'll lay my hands on this.