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

Is a generic newtype possible? #130

Closed
atezet opened this issue Mar 25, 2024 · 12 comments
Closed

Is a generic newtype possible? #130

atezet opened this issue Mar 25, 2024 · 12 comments
Assignees

Comments

@atezet
Copy link
Contributor

atezet commented Mar 25, 2024

Is it possible to create a generic newtype with nutype? When I do, let's say:

use nutype::nutype;

#[nutype(validate(predicate = |v| !v.is_empty()))]
struct NonEmptyVec<T>(Vec<T>);

I get:

 --> src/main.rs:4:27
  |
4 | struct NonEmptyVec<T>(Vec<T>);
  |                           ^ not found in this scope
  |
help: you might be missing a type parameter
  |
4 | struct NonEmptyVec<T><T>(Vec<T>);
  |                   +++
@greyblake
Copy link
Owner

Hi @atezet

It's not possible at the moment.
But I have this in mind and want to make it possible in the future.

@atezet
Copy link
Contributor Author

atezet commented Mar 27, 2024

I already like using the library as-is a lot, but definitely looking forward to that! Thanks

@greyblake
Copy link
Owner

@atezet Btw, if you need only non empty vector, I recommend taking a look at nonempty: https://github.com/cloudhead/nonempty

@atezet
Copy link
Contributor Author

atezet commented Mar 30, 2024

Thanks for the suggestion! We tried that at first, and iirc it worked for the cases where we needed a vector, but we also need IndexMap and some others. Unfortunately nonempty only supports Vec, even though its name would suggest otherwise.

I also suggested just using nutype and fixing the type of the stored values, but my colleague went ahead and implemented a newtype wrapping a generic Iterator for now.

Would love to see this to be possible with nutype or nonempty.

@greyblake
Copy link
Owner

The basics implementation to support generics is merged in #135

There some corner cases that need to be addressed before a new version can be published, see generics label.

@atezet If would appreciate if you can try the current implementation out and let me know if there something else missing!

@atezet
Copy link
Contributor Author

atezet commented Jun 4, 2024

Hey there, I just briefly checked out the interface (no time to check the code), and my example works as expected! I think your observations regarding support for trait bounds and auto derives are very correct. It would be cool if something like (there are better predicates to do this):

#[nutype(validate(predicate = |i| i.into_iter().count() != 0))]
struct NonEmpty<T: IntoIterator>(T);

would work as well, but that should be covered by #142

@greyblake
Copy link
Owner

@atezet Hi, thanks for the feedback!

@greyblake
Copy link
Owner

@atezet I just finished the work with generics and published 0.4.3-beta.1. Let me know if that works for you.
I am planning to release 0.4.3 in a week.

@greyblake
Copy link
Owner

@atezet So in 0.4.3 it's possible to use generics and to set type bounds.
However, to properly implement your use case it would require setting type bounds with where clause (this one is not supported yet):

#[nutype(
    validate(predicate = |c| c.into_iter().next().is_some()),
)]
struct NonEmpty<C>(C)
where
    for<'a> &'a C: IntoIterator;

The problem here is that we need to set the &C: IntoIterator boundary, but within <C> it's only possible to set the boundary on C, (not &C).

As a workaround, it's possible to clone. But of course it's not optimal from the performance perspective:

#[nutype(
    validate(predicate = |c| c.clone().into_iter().next().is_some())
)]
struct NonEmpty<C: Clone + IntoIterator>(C);

@greyblake
Copy link
Owner

greyblake commented Jul 8, 2024

@atezet I've found a good solution for you.
We can introduce a helping IsEmpty trait and with that workaround the limitation without cloning:

use nutype::nutype;

trait IsEmpty {
    fn is_empty(&self) -> bool;
}

impl<T> IsEmpty for T
where
    for<'a> &'a T: IntoIterator,
{
    fn is_empty(&self) -> bool {
        self.into_iter().next().is_none()
    }
}

#[nutype(
    validate(predicate = |c| !c.is_empty()),
)]
struct NonEmpty<C: IsEmpty>(C);


fn main() {
    let number_vector = NonEmpty::try_new(vec![1, 2, 3]).unwrap();

    let chars: std::collections::BTreeSet<char> = "abc".chars().collect();
    let char_set = NonEmpty::try_new(chars).unwrap();
}

@atezet
Copy link
Contributor Author

atezet commented Jul 9, 2024

Hi @greyblake. Sorry for not replying earlier. We're in a final sprint before the code is audited, so I've some quite busy times and will look at using your solution in the next phase. Thanks for putting in the work, I think it looks quite clean already! I really like what nutype can do for typesafe type driven development with minimal boilerplate

@greyblake
Copy link
Owner

@atezet No worries! Good luck with the sprint!
Looking forward for any feedback from you, if you'll have any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Status: Done
Development

No branches or pull requests

2 participants