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 more formatting traits #56

Merged
merged 26 commits into from
Mar 21, 2020
Merged

add more formatting traits #56

merged 26 commits into from
Mar 21, 2020

Conversation

maxbla
Copy link
Contributor

@maxbla maxbla commented Jul 22, 2019

fixes #1
TODO:

  • eliminate heap use for no_std (does not support padding)
    • add better formatting tests for no_std
  • write a macro for formatting trait impls
    • simplify macro for formatting trait impls using format_args!

@maxbla
Copy link
Contributor Author

maxbla commented Jul 22, 2019

As it stands right now, this PR is a breaking change because it makes Display only work on types that are Clone + Integer. I did this so I could properly deal with both padding and negative signs, although I may have to remove that feature (for Display, but keep it for the new formatting traits) to maintain backwards compatibility

Edit: The PR properly deals with padding and negative signs for new formatting traits, but not Display, so as to maintain backwards compatibility

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@maxbla
Copy link
Contributor Author

maxbla commented Jul 25, 2019

I never said it, but this is intended to fix #1

@maxbla
Copy link
Contributor Author

maxbla commented Aug 9, 2019

I can't figure out how to use std::format! in crates using no_std for older rust versions. When I type std::format!(), it works in new rust, but rust 1.26 and 1.15 don't work.

error[E0658]: non-ident macro paths are experimental (see issue #35896)

    --> src/lib.rs:1096:13

     |

1096 |             std::format!("{:o}", self.numer)

     |             ^^^^^^^^^^^

(see Travis for full output)
If I try to use format!(with #[macro_use] on extern crate std and use std::format) it just fails error: macro undefined: format! I could easily

@cuviper
Copy link
Member

cuviper commented Aug 9, 2019

The format! macro creates a String -- that's only possible on no_std targets with the alloc crate, which was only stabilized in Rust 1.36. I'm not sure if all no_std targets are guaranteed to have alloc even then.

@maxbla
Copy link
Contributor Author

maxbla commented Aug 9, 2019

I'm also setting the #[cfg(feature = "std")] attribute for the method that I'm trying to use format! in, so that should allow use of std::string::String, right? Also the code I wrote works in rust 1.31.0 and current stable according to TravisCI.

@cuviper
Copy link
Member

cuviper commented Aug 9, 2019

Ah, note how std is imported:

#[cfg(feature = "std")]
#[cfg_attr(test, macro_use)]
extern crate std;

Make that a direct #[macro_use] and you should be able to use format! directly.

I think the std::format! style was added in 1.30.0, as part of macro use statements.

Macros exported with #[macro_export] are now placed into the root module of the crate.

@maxbla
Copy link
Contributor Author

maxbla commented Aug 13, 2019

This PR is no longer a breaking change -- the original Display trait remains unchanged, with no support for padding. I will make a PR with the breaking changes to Display in it in the future that may be merged at the same time as other breaking changes. It may make sense to include an implementation of Display specific to no_std and one (more fully featured) that depends on std::string::String.

That said, I think this PR is ready for review.

src/lib.rs Outdated
assert_eq!(&format!("{:#010b}", _1_2), "0b001/0b10");
let half_i8: Ratio<i8> = Ratio::new(1_i8, 2_i8);
assert_eq!(&format!("{:b}", -half_i8), "11111111/10");
assert_eq!(&format!("{:#b}", -half_i8), "0b11111111/0b10");
Copy link
Member

Choose a reason for hiding this comment

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

Ew -- I find it unfortunate that negative numbers are formatted this way, especially since they'll overflow if you try to parse it back, but I know it's not your doing. If you're curious, it comes from core's int_base! casting as unsigned.

@cuviper
Copy link
Member

cuviper commented Aug 15, 2019

I should have mentioned before, num-complex goes through similar pains to format multiple numbers simultaneously, including std/no_std. One trick you might like is using format_args! into a nested function (necessary for its borrowed lifetimes) to let it work without format! strings. It's all wrapped in a write_complex! macro to be shared among the various traits.

@cuviper
Copy link
Member

cuviper commented Aug 15, 2019

You don't have to match that complexity (pun!) but it may show you some useful tricks.

src/lib.rs Show resolved Hide resolved
@cuviper cuviper mentioned this pull request Jan 10, 2020
3 tasks
@cuviper
Copy link
Member

cuviper commented Feb 17, 2020

I would like to include this in 0.3 (#65), and notably this means we can make the breaking change you had earlier on Display. Can you rebase this, and perhaps make that change, so I can give it another review?

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@maxbla maxbla changed the title WIP: add more formatting traits add more formatting traits Mar 18, 2020
maxbla and others added 19 commits March 18, 2020 12:45
Co-Authored-By: Josh Stone <[email protected]>
previously formatting would fail the following test case
format!("{:+}", Ratio:new(-2, 1)), returning "+-2", utter nonsense.
This fix adds a check for a negative sign (dash) in an intermediate
string representation, removing it if found. no_std targets
previously didn't support sign_plus(), but now do.
Now the correct features will  be enabled when running the ci script
outside of ci environments.
Use autofcg to detect if integers support exponential formatting.
If so, autocfg sets the has_int_exp_fmt cfg, used in tests.
@maxbla
Copy link
Contributor Author

maxbla commented Mar 19, 2020

There's one more thing this doesn't support - precision. Is it worth it? Is there a way to do it that doesn't introduce much more complexity?

@cuviper
Copy link
Member

cuviper commented Mar 19, 2020

Precision would really only be useful for the Exp formats, right? I think it's fine to defer that.

Copy link
Member

@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

I'd like the build script to be simpler, but then I think we're ready to merge!

build.rs Outdated
&& ac.probe_expression("format!(\"{:e}\", 0_u64)")
&& ac.probe_expression("format!(\"{:e}\", 0_u128)"))
{
panic!("Some integer types implement *Exp traits, but not others")
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit overkill, and has a tangible effect on compile times -- I measured that running the build script on current stable takes ~110ms, whereas nightly takes ~680ms. After all, each probe requires a new rustc process invocation.

If you really want to test them all, it could be done better in one large probe expression. However, we really only care about this feature for testing purposes, so I don't think it needs to be so thorough. For that matter, the test only uses it for Rational = Ratio<isize>, which you didn't even probe... 😉

Let's just probe for support in isize and call it good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

each probe requires a new rustc process invocation

I didn't even think about how it worked, but that makes sense.

Let's just probe for support in isize and call it good.

Sounds good.

@maxbla
Copy link
Contributor Author

maxbla commented Mar 20, 2020

Precision would really only be useful for the Exp formats, right?

Yep, that's right.

I think it's fine to defer that.

Agreed. I don't want to deal with it now and *Exp trait implementations for Integers hasn't landed in stable yet.

I'm considering writing an RFC about how extending formatting (as this crate does) is more difficult than it needs to be because of the api. Off the top of my head, having sign_minus mean never render a sign would be nice, and there should be some way to say default precision with the .* precision specifier (maybe by taking a type that is Into<Option<usize>>, so None works). I'm not sure how this should work, but it would also be nice if there was some way to turn a type + formatting trait + formatter into a string. i.e. not need to generate a formatting string. This could work by creating a "mock" formatter that looks like a normal formatter, but writes to a String instead of to stdout, or whatever.

@cuviper
Copy link
Member

cuviper commented Mar 21, 2020

Let's ship it!

bors r+

@bors
Copy link
Contributor

bors bot commented Mar 21, 2020

Build succeeded

@bors bors bot merged commit 47d63e2 into rust-num:master Mar 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement more formatting traits
2 participants