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

Added TryFrom impl generation for target types behind feature flag #77

Closed
wants to merge 16 commits into from

Conversation

TedDriggs
Copy link
Collaborator

This addresses #76. The generation is automatic but depends on crate feature try_from.

@TedDriggs
Copy link
Collaborator Author

@colin-kiegel The failed test looks to be related to a failure in rustup; don't know what to make of that.

@colin-kiegel
Copy link
Owner

That's rust-lang/rustup#1062 and should be gone with the next nightly release. I can hit restart over the weekend and also have a look at the PR. :-)

Copy link
Owner

@colin-kiegel colin-kiegel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx - good start!

Please merge (or rebase to) current master, because I basically removed the derive_builder_test crate. All integration tests in derive_builder/tests/ will then need this line:

#![cfg_attr(feature = "try_from", feature(try_from))]

@@ -23,6 +23,7 @@ proc-macro = true

[features]
logging = [ "log", "env_logger", "derive_builder_core/logging" ]
try_from = []
struct_default = []
skeptic_tests = ["skeptic"]
nightlytests = []
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's change this to nightlytests = ["try_from"] - this will in turn activate it in travis and dev/nightlytests.sh / dev/githook.sh.

BuilderPattern::Mutable |
BuilderPattern::Immutable => quote!(&self),
};
let self_param = self.pattern.to_build_method_tokens();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep this match statement in BuildMethod for the moment. I feel like from the perspective of unit testing this gives clearer boundaries of responsibility. Of course this makes the BuildMethod type a little more complex..

}
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this back to BuildMethod for the moment.

let string_ty = self.bindings.string_ty();

let immutable_borrow = quote!(
impl #impl_generics #try_from<&'unused #builder_ty #ty_generics> for #target_ty #ty_generics
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we elide the lifetime parameter 'unused and write & #builder_ty #ty_generics instead?
Would that fail if #ty_generics contained other lifetime parameters? Did you test that?

If we really need to have an explicit lifetime parameter, I'd suggest to call it 'try_from just in case so the user would get nicer error messages - while still reducing the risk of name collisions.

#where_clause {
type Error = #string_ty;

fn try_from(v: &#builder_ty #ty_generics) -> #result_ty<Self, Self::Error> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't have a lifetime parameter here, that makes me suspicious that we might not need it. :-)


#mutable_borrow

#owned
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to start with only one impl depending on the builder pattern

Variant 1: Mimic build() method

Pseudo code for demonstration:

Owned => /* ... impl TryFrom<FooBuilder> for Foo ... */,
Mutable |
Immutable => /* ... impl TryFrom<&FooBuilder> for Foo ... */,

So despite having three variants of BuilderPattern there would only be two variants of TryFrom implementations - in analogy to the BuildMethod.

Variant 2: Always take Self

The logic behind changing the signature of the build method was to enable method chaining FooBuilder::default().foo(1).build(). But this would most likely not be the primary use case of TryFrom - although it could be used in this position.

Alternatively we could talk about always implementing TryFrom<FooBuilder> for Foo and never implementing TryFrom<&FooBuilder> for Foo.

Discussion

We should probably discuss the primary use cases for having TryFrom implementations. What signature would they require? Would autoderef or other coercions help to pick the right TryFrom implementation?

@@ -9,7 +9,7 @@ publish = false
path = "src/lib.rs"

[features]
nightlytests = ["compiletest_rs", "derive_builder/nightlytests"]
nightlytests = ["compiletest_rs", "derive_builder/nightlytests", "derive_builder/try_from"]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the derive_builder_test crate in latest master. :-)

PS: More correctly - I merged it into the derive_builder crate and left a tiny derive_builder/tests/dummy-crate for doc-inspection.

@@ -0,0 +1,37 @@
#![cfg(feature = "nightlytests")]
#![cfg_attr(feature = "nightlytests", feature(try_from))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please merge (or rebase to) current master. Then depend on feature = "try_from" instead.

@TedDriggs
Copy link
Collaborator Author

@colin-kiegel are the run-pass tests not passed the flags from the crate?

@colin-kiegel
Copy link
Owner

@TedDriggs Hm, it would seem so. You could try something like this for the compiletests.

-#![cfg_attr(feature = "nightlytests", feature(try_from))]
+// Compiletests seem to _not_ have access to feature flags. We need to set `#![feature(try_from)]` unconditionally.
+#![allow(unused_features)]
+#![feature(try_from)]

@TedDriggs
Copy link
Collaborator Author

Now it's complaining about the example in readme.md. I'm stumped on how to get this working without a separate feature flag gating the generation of the TryFrom blocks.

colin-kiegel and others added 5 commits April 21, 2017 14:42
includes a workaround for rust-lang/rust#41401
we should not silently implement the `TryFrom` trait if the build
function is skipped, because the current `TryFrom` implementation
delegates to a build function.
@colin-kiegel
Copy link
Owner

I imagine the final TryFrom implementation like this:

  • the build-method will only be a shallow wrapper around the TryFrom implementation
    • all logic (like custom validation) should be implemented in the TryFrom implementation
    • the build method will require the TryFrom implementation - not the other way around, like now
    • the build_fn will follow the builder pattern and clone when necessary, before delegating to TryFrom.
    • the TryFrom implementation always consumes FooBuilder and avoids cloning. (I believe this would be the most idiomatic implementation)

It's not a problem to start with TryFrom delegating to the build fn (i.e. in the other direction), before we remove the feature gate. However IMO we should already do the following change in this PR:

  • TryFrom should always consume FooBuilder (i.e. not take references)

What do you think about this?

@colin-kiegel
Copy link
Owner

PS: If you happen to agree to this change than IMO we would be ready to merge. Otherwise we can of course discuss this more deeply. :-)

@TedDriggs
Copy link
Collaborator Author

the build-method will only be a shallow wrapper around the TryFrom implementation

My impression after #54 was that logic should live in inherent methods, with the trait impl being the shallow wrapper.

TryFrom should always consume FooBuilder (i.e. not take references)

Without the version that takes a reference, using this in conjunction with #72 gets more verbose. The setter methods are returning &mut Self by default, so not having impl<'a> TryFrom<&'a mut FooBuilder> for Foo means you can't write:

let bar = BarBuilder::default()
    .try_baz("1.2.3.4")?
    .try_foo(FooBuilder::default()
        .lorem("Hello")
        .ipsum("world"))?    
    .build()?;

I'm not opposed to having an owned, non-cloning implementation, as long as Rust is smart enough to avoid an extra clone to push into that implementation if someone writes the code above.

@colin-kiegel
Copy link
Owner

colin-kiegel commented Apr 22, 2017

My impression after #54 was that logic should live in inherent methods, with the trait impl being the shallow wrapper.

Hm, but it does not make a difference with regard to inference whether a trait impl delegates to an inherent method or vice-versa. The main issue in #54 was that things like .try_into().try_into().try_into() have inference problems. But inherent methods .foo().bar().baz() do not have this problem, even if each one of them delegates to a trait impl.

The setter methods are returning &mut Self by default

I'm actually thinking about changing that default to Self. I started a discussion #88 about this - but I don't intend to rush anything there, because we already did a flip in the past.

@colin-kiegel
Copy link
Owner

colin-kiegel commented Apr 24, 2017

It's already possible to use nested builders even with the mutable pattern. You only need to write .foo(foo_builder.bar(v).build()?) instead of .try_foo(foo_builder.bar(v))?.

I think we can still introduce the TryFrom<&FooBuilder> implementation later. It would be less of a breaking change if we introduce it later rather than remove it later.

Which reminds me that we should raise the minor version with this feature. EDIT: As soon as we remove the feature gate of course. ^^

PS: I'd also welcome more independent opinions on this topic.

@TedDriggs TedDriggs closed this May 30, 2018
@TedDriggs TedDriggs deleted the try_from branch February 5, 2019 15:21
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

Successfully merging this pull request may close these issues.

2 participants