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

Add {To,From}Primitive conversion trait, add #[deriving(FromPrimitive)] for enums #9250

Merged
merged 13 commits into from
Oct 5, 2013

Conversation

erickt
Copy link
Contributor

@erickt erickt commented Sep 17, 2013

This PR solves one of the pain points with c-style enums. Simplifies writing a fn to convert from an int/uint to an enum. It does this through a #[deriving(FromPrimitive)] syntax extension.

Before this is committed though, we need to discuss if ToPrimitive/FromPrimitive has the right design (cc #4819). I've changed all the .to_int() and from_int() style functions to return Option<int> so we can handle partial functions. For this PR though only enums and extra::num::bigint::* take advantage of returning None for unrepresentable values. In the long run it'd be better if i64.to_i8() returned None if the value was too large, but I'll save this for a future PR.

Closes #3868.

#[inline]
fn to_i64(&self) -> Option<i64> {
// XXX: Check for range.
self.to_int().and_then(|x| Some(x as i64))
Copy link
Member

Choose a reason for hiding this comment

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

Is this correct on 32-bit platforms? (I guess it's not, and so, should this truncation be the default behaviour?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change the unimplemented version to be .to_i64(). I was guessing the people would reach first towards implementing .to_int() and .to_uint(), but you're right, it'd be better to avoid truncation by default.

@huonw
Copy link
Member

huonw commented Sep 17, 2013

Looks pretty nifty, will certainly reduce the number of transmutes people are driven to. I think this would benefit from some negative tests also: e.g. I think the following should all fail with nice error messages.

#[deriving(FromPrimitive)]
struct Foo { x: int }

#[deriving(FromPrimitive)]
enum Bar { A(int), B(uint) }

#[deriving(FromPrimitive)]
enum Baz { S { x: int } }

so we should test to stop them regressing accidentally.

return match *substr.fields {
StaticEnum(enum_def, _) => {
if enum_def.variants.is_empty() {
cx.span_fatal(span, "`FromPrimitive` cannot be derived for enums with no variants");
Copy link
Member

Choose a reason for hiding this comment

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

I think this still makes sense, and probably doesn't need special treatment at all, since the loop below will just not run?

It just returns None on every possible conversion: i.e. having 0 variants mean the numbers in the (empty) set [0, 0) are valid to convert from, and anything else (i.e. everything) gives None.

@huonw
Copy link
Member

huonw commented Sep 19, 2013

I'd be happy to r+ this, but it seems like a large enough change to be worth getting input from someone else.

impl_to_primitive!(f64)
impl_to_primitive!(float)

/// A generic trait for converting an number to a value.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: s/an/a

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks! I've got it fixed in my upcoming rebase of this PR.

@erickt
Copy link
Contributor Author

erickt commented Sep 23, 2013

This has been rebased to HEAD and is ready for review again.

#[deriving(FromPrimitive)]
struct A { x: int }
//~^^ ERROR `FromPrimitive` cannot be derived for structs
//~^^^ ERROR `FromPrimitive` cannot be derived for structs
Copy link
Member

Choose a reason for hiding this comment

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

Why are each of the error messages doubled in this test? I would imagine that only one should be printed each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Downside to the #[deriving(FromPrimitive)] implementation. syntax::ext::deriving::generic::* works a method at a time. Because FromPrimitive requires at least two methods to be implemented, both generating both methods ends up reporting an error. This wouldn't be that difficult to change. We would just have to modify TraitDef.expand_struct_def and TraitDef.expand_enum_def to return either an Option<@ast::item> or Result<@ast::item,~str> and use that to signal that it's not worth implementing the rest of the methods.

@alexcrichton
Copy link
Member

I'm a little uneasy with this change in general. I think that the deriving(FromPrimitive) for enums is pretty awesome, but otherwise it doesn't quite sit well with me. It seems like a bit of a regression going from writing BigInt::new(int_value) to FromPrimitive::from_int(int_value), and there's a lot of truncation going on in both the from and the to case which isn't well documented.

That being said, this seems like it might be a brainchild of discussion in #4819, which I haven't been following that closely. Is there an intent on using {From,To}Primitive in many use-cases other than the big numbers and in deriving-situations? In general it's a bit of a pain to explicitly call static methods on traits (it's not entirely clear that the trait is implemented for the type), so I thought we were attempting to avoid them in general.

@huonw
Copy link
Member

huonw commented Sep 24, 2013

@alexcrichton I'd say that if we're going to be avoid static methods in traits because they're hard to use, we need to fix that, rather than just avoiding them entirely even when they are the appropriate thing to use.

(Also, I think that *Primitive as implemented here are an improvement over NumCast, e.g. they are checked for overflow.)

@erickt
Copy link
Contributor Author

erickt commented Sep 24, 2013

@alexcrichton: This PR only partially implements my plans, I wanted to submit it early before I invested too much into this work in case the community decided I was going in a wrong direction. There's a couple things left to do:

  • Implement #[deriving(FromPrimitive)] for struct Foo(int) tuple structs.
  • Implement #[deriving(ToPrimitive)] for c style enums and struct Foo(int) tuple structs.
  • Fix the BigInt trunction, which should be pretty simple.
  • Add ToBigInt and ToBigUint traits and add impls for the primitive numeric types.
  • Actually add checks for under/overflow to .to_int(), from_uint(uint_value) and etc.

Finally, I agree it is a pain to write FromPrimitive::from_int(int_value), that's why I added some helper functions like num::from_int(int_value) that just calls the trait method.

@emberian
Copy link
Member

This needs a rebase.

@huonw
Copy link
Member

huonw commented Sep 27, 2013

r=me if this gets consensus.

@erickt
Copy link
Contributor Author

erickt commented Sep 27, 2013

This latest version adds:

  • bounds checking on ints and uints, but not yet for floats.
  • Fixes the truncation issue with BigInts
  • Adds ToBigInt and ToBigUint

@brson
Copy link
Contributor

brson commented Sep 28, 2013

I r+'ed this. It's pretty big and I haven't followed it closely, but the response appears to be mostly favorable. Like all things it can be tweaked later.

@huonw
Copy link
Member

huonw commented Sep 29, 2013

The run-pass tests need pub fn main for the windows test runner.

erickt added 5 commits October 2, 2013 07:55
Right now this only works for c-style enums.
…rsion

One downside with this current implementation is that since BigInt's
default is now 64 bit, we can convert larger BigInt's to a primitive,
however the current implementation on 32 bit architectures does not
take advantage of this fact.
bors added a commit that referenced this pull request Oct 5, 2013
This PR solves one of the pain points with c-style enums. Simplifies writing a fn to convert from an int/uint to an enum. It does this through a `#[deriving(FromPrimitive)]` syntax extension.

Before this is committed though, we need to discuss if `ToPrimitive`/`FromPrimitive` has the right design (cc #4819). I've changed all the `.to_int()` and `from_int()` style functions to return `Option<int>` so we can handle partial functions. For this PR though only enums and `extra::num::bigint::*` take advantage of returning None for unrepresentable values. In the long run it'd be better if `i64.to_i8()` returned `None` if the value was too large, but I'll save this for a future PR.

Closes #3868.
@bors bors closed this Oct 5, 2013
@bors bors merged commit 0e8ad4d into rust-lang:master Oct 5, 2013
/// Converts the value of `self` to an `u64`.
#[inline]
fn to_u64(&self) -> Option<u64> {
self.to_u64().and_then(|x| x.to_u64())
Copy link
Member

Choose a reason for hiding this comment

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

I venture to suggest this is not what was intended. self.to_i64(), perhaps? (Either that or omit the default implementation.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I'll fix it.

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.

Syntax extension for converting enums to/from integers
8 participants