-
Notifications
You must be signed in to change notification settings - Fork 52
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
#[builder(default = ...)] for generic field got error "expected type parameter, found concrete type" #44
Comments
This is the correct behavior. The default value |
Thanks for your reply! I agree that Let's think about By using In other words, by giving a generic field a default value, then I hope the macro can transform Currently what is going wrong is the direction of type inference: we get |
The TL;DR is, code // Compiling error
#[derive(TypedBuilder)]
struct Foo<T> {
#[builder(default = 12)]
x: T,
}
Foo::builder().build() should equal to // OK
#[derive(TypedBuilder)]
struct Foo<T> {
x: T,
}
Foo::builder().x(12).build() |
So I'm not sure this is even possible in Rust. The And even if Rust had such a facility - best I could do is support a default that only works is the generic parameter is already inferred to be an integer by something else. Maybe Rust's type inference is powerful enough to take you the rest of the way - but I seriously doubt it. Finally - this feels like breaking the Rust rule that the information about the types should be in the signatures, not in the implementation body. And yes, you can argue that the attributes are part of the signature, but |
I think you are right. I tried to implement But I can implement struct Foo<T> {
x: T,
}
impl Default for Foo<i32> {
fn default() -> Self {
Foo { x: 12 }
}
}
Foo::default()
So I'm thinking If we can let the #[derive(Debug)]
struct Foo<X> {
x: X,
}
impl<X> Foo<X> {
#[allow(dead_code)]
fn builder() -> FooBuilder<((),), X> {
FooBuilder {
fields: ((),),
_phantom: core::default::Default::default(),
}
}
}
#[must_use]
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
struct FooBuilder<TypedBuilderFields, X> {
fields: TypedBuilderFields,
_phantom: core::marker::PhantomData<X>,
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub trait FooBuilder_Optional_x<T> {
fn into_value(self) -> T;
}
impl FooBuilder_Optional_x<i32> for () {
fn into_value(self) -> i32 {
12 // I edited the trait to return default value directly from the impl
}
}
impl<T> FooBuilder_Optional_x<T> for (T,) {
fn into_value(self) -> T {
self.0
}
}
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<X> FooBuilder<((),), X> {
pub fn x(self, x: X) -> FooBuilder<((X,),), X> {
let x = (x,);
let (_,) = self.fields;
FooBuilder {
fields: (x,),
_phantom: self._phantom,
}
}
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub enum FooBuilder_Error_Repeated_field_x {}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<X> FooBuilder<((X,),), X> {
#[deprecated(note = "Repeated field x")]
pub fn x(self, _: FooBuilder_Error_Repeated_field_x) -> FooBuilder<((X,),), X> {
self
}
}
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<X, __x: FooBuilder_Optional_x<X>> FooBuilder<(__x,), X> {
pub fn build(self) -> Foo<X> {
let (x,) = self.fields;
let x = FooBuilder_Optional_x::into_value(x);
Foo { x }
}
}
fn main() {
dbg!(Foo::builder().build()); // works, x = 12
dbg!(Foo::builder().x("hello").build()); // works, x = "hello"
dbg!(Foo::<String>::builder().build()); // error[E0599]: no method named `build` found for struct `FooBuilder<((),), String>` in the current scope
} That works well except for the error message, and my initial try found that for better error message I will get "duplicate definition for |
I found a workaround for my use case. What I'm doing is storing an optional callback in a struct: #[derive(TypedBuilder)]
struct Foo<F>
where
F: FnMut(),
{
#[builder(default = None)]
f: Option<F>,
} The code above does compile well. (It's weird that To build the struct, I need to specify the type: @idanarye you can close this if you think it's impossible to make the initial code work. |
Oh... your type is a closure. Suddenly your problem makes much more sense... Your solution will not work. Yes, it will compile, and yes, you can build Frankly - I'm not even sure if what you want can be done in Rust... If you are willing to take a slight performance hit, you can box a dynamic closure: #[derive(typed_builder::TypedBuilder)]
struct Foo
{
#[builder(default = Box::new(|| println!("default f")))]
f: Box<dyn FnMut()>,
}
fn main() {
(Foo::builder().build().f)();
(Foo::builder().f(Box::new(|| println!("nondefault f"))).build().f)();
} I can even throw in a |
Yes I'm going to use boxed closure 😄 I just found that the default value of type parameter may help here, just like pub struct Lazy<T, F = fn() -> T> { ... } And it's Currently, in the generated Just for more context: I'm writing a crate which brings Flutter's layout style to TUI world, and there's a proc-macro build! {
Padding {
padding: EdgeInsets { left: 2, },
child: Text {
text: "Hello world!",
},
}
} expands to Padding::builder()
.padding(EdgeInsets::builder().left(2).build())
.child(Text::builder().text("Hello world!\n").build())
.build() In my crate I store the event handler callback in a struct. |
Additional comment here. This builder pattern is actually in use in very popular Rust libraries. For example, in the The default builder use a |
The text was updated successfully, but these errors were encountered: