Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible rustc language features for less magic? #71

Open
johannescpk opened this issue Sep 26, 2022 · 10 comments
Open

Possible rustc language features for less magic? #71

johannescpk opened this issue Sep 26, 2022 · 10 comments

Comments

@johannescpk
Copy link

johannescpk commented Sep 26, 2022

Was just thinking which kind of language features could help with a cleaner approach compared "impl on tuple shadowing" and "sealed enums" – which is a creative way to solve it for sure πŸ‘. Are you already aware of RFCs that touch on those? Something that came to mind, but no idea if it's feasable: "Option must be Some"-attribute that makes the compiler error if option is None

struct UwuBuilder {
  #[option_must_be_some_compile_error(message = "owo is required")]
  owo: Option<usize>,
}
@idanarye
Copy link
Owner

If an Option cannot be None - why not just make it a non-Option?

@johannescpk
Copy link
Author

johannescpk commented Sep 27, 2022

#[derive(Default)]
struct UwuBuilder {
  /// required
  #[option_must_be_some_compile_error(message = "owo is required")]
  owo: Option<usize>,

  /// optional
  uwu: Option<usize>,
}

impl UwuBuilder {
  fn owo(self, owo) -> Self {
    self.owo = Some(owo);
    self
  }

  fn uwu(self, uwu) -> Self {
    self.uwu = Some(uwu);
    self
  }

  fn build(self) {}
}

UwuBuilder::default().owo(1).uwu(2).build(); // Compiles
UwuBuilder::default().uwu(2).build(); // Does not compile, because `UwuBuilder::owo` is `None`

That would be idea. Makes more sense? But as I said, no idea if it's feasible, especially in terms of type inference. Also adjusted the initial description, as "panic" is misleading, "compiler error" was what I meant.

Generally I feel like there's a language feature hiding to help make this whole thing less magic, which would be good, right?

@idanarye
Copy link
Owner

idanarye commented Oct 4, 2022

Why not this?

#[derive(Default)]
struct UwuBuilder {
  /// required
  owo: usize,

  /// optional
  uwu: Option<usize>,
}

@johannescpk
Copy link
Author

johannescpk commented Oct 5, 2022

Then you can't have a builder, can you? Because you would need to instantiate UwuBuilder with owo already set, which does not allow to have the builder pattern.

@idanarye
Copy link
Owner

idanarye commented Oct 5, 2022

Oh. I see your point now - I've somehow missed that you were defining an UwuBuilder struct and not an Uwu struct. But in this crate I went for a different approach - you would define the Uwu struct and let the derive macro create the UwuBuilder, which is generic and its generic parameter encodes which fields were set already. So an UwuBuilder where owo is set has a different type (because of the generic parameter) than an UwuBuilder where owo is not set, and only the former will have a build method.

@johannescpk
Copy link
Author

johannescpk commented Oct 17, 2022

Yeah, I know how your crate is working. The issue was meant to talk about possible language features to not require the hacks that your crate is doing to enable the behavior it's providing – because generating impls on different combinations of tuples ("tuple shadowing" in the initial post) and having an enum without variants so you can't call the build function ("sealed enum" in the initial post) are hacks. Creative hacks, but hacks.

@johannescpk
Copy link
Author

Sorry if that sounded a bit rude. And if you feel like this is out of scope for your crate specifically, feel free to close.

@idanarye
Copy link
Owner

Well, if we are talking about language features, how about something like this?

struct UwuBuilder<const D: Definition<Self>>  {
    /// required
    owo: usize,

    /// optional
    #[omittable]
    uwu: usize,
}

impl<const D: Definition<UwuBuilder>> UwuBuilder<D> {
    fn build(self) -> Uwu {
        Uwu {
            owo: self.owo,

            #[if(D.has("uwu"))]
            uwu: self.uwu,

            #[if(!D.has("uwu"))]
            uwu: 42,
        }
    }
}

And use it like this:

assert_eq(
    UwuBuilder {
        owo: 1,
    }.build(),
    Uwu {
        owo: 1,
        uwu: 42,
    },
);

assert_eq(
    UwuBuilder {
        owo: 2,
        uwu: 3,
    }.build(),
    Uwu {
        owo: 2,
        uwu: 3,
    },
);

@johannescpk
Copy link
Author

johannescpk commented Nov 9, 2022

Interesting approach!

But isn't the tricky part of the generated builder struct that you need to have all fields be Options, so that the struct can get initialized completely empty, and then only fill the Options with Some after constructing via method calls? So your

struct UwuBuilder<const D: Definition<Self>>  {
    /// required
    owo: usize,

wouldn't work, right?

@idanarye
Copy link
Owner

idanarye commented Nov 9, 2022

"Starting empty" is not really a core requirement of the builder pattern. One could imaging a builder that works like this:

assert_eq!(
    Uwu.builder(2).uwu(3).build(),
    Uwu { owo: 2, uwu: 3 },
);

Or even:

assert_eq!(
    UwuBuilder { owo: 2 }.uwu(3).build(),
    Uwu { owo: 2, uwu: 3 },
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants