derive_more icon indicating copy to clipboard operation
derive_more copied to clipboard

Feature: derive Getter/Setter

Open tyranron opened this issue 4 years ago • 9 comments

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
    }
}

tyranron avatar Jul 21 '20 08:07 tyranron

If this is something desirable, I'll submit the PR in a week or two.

tyranron avatar Jul 21 '20 08:07 tyranron

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 avatar Nov 22 '20 06:11 ronlobo

@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.

tyranron avatar Nov 22 '20 15:11 tyranron

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 avatar Nov 22 '20 18:11 ronlobo

@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.

tyranron avatar Nov 23 '20 16:11 tyranron

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.

ronlobo avatar Nov 24 '20 23:11 ronlobo

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:

  1. 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?
  2. For owned, lets make the default suffix cloned? Since that's what it does.
  3. Like the design proposes, let's only implement Getter for now, not Setter.

JelteF avatar Mar 07 '21 11:03 JelteF

Is this something you still care about @tyranron?

JelteF avatar Dec 21 '23 23:12 JelteF

@JelteF yup, it just has a very low priority in my agenda regarding derive_more. Someday, I'll lay my hands on this.

tyranron avatar Dec 22 '23 12:12 tyranron