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

Implement a format_args!() macro #9012

Closed
wants to merge 1 commit into from

Conversation

alexcrichton
Copy link
Member

The purpose of this macro is to further reduce the number of allocations which
occur when dealing with formatting strings. This macro will perform all of the
static analysis necessary to validate that a format string is safe, and then it
will wrap up the "format string" into an opaque struct which can then be passed
around.

Two safe functions are added (write/format) which take this opaque argument
structure, unwrap it, and then call the unsafe version of write/format (in an
unsafe block). Other than these two functions, it is not intended for anyone to
ever look inside this opaque struct.

The macro looks a bit odd, but mostly because of rvalue lifetimes this is the
only way for it to be safe that I know of.

Example use-cases of this are:

  • third-party libraries can use the default formatting syntax without any
    forced allocations
  • the fail!() macro can avoid allocating the format string
  • the logging macros can avoid allocation any strings

I plan on transitioning the standard logging/failing to using these macros soon. This is currently blocking on inner statics being usable in cross-crate situations (still tracking down bugs there), but this will hopefully be coming soon!

Additionally, I'd rather settle on a name now than later, so if anyone has a better suggestion other than format_args, I'm not attached to the name at all :)

@alexcrichton
Copy link
Member Author

An example of the syntactic expansion is

fn main() {
    format_args!(|test| { use_test(test) }, "{}", 2);
}

to

priv use std::prelude::*;
priv extern mod std;

mod __std_macros {
    #[macro_escape];
    #[doc(hidden)];
    priv use std::prelude::*;
}
fn main() {
    {
        #[address_insignificant]
        pub static __static_fmtstr: [::std::fmt::rt::Piece<'static>, ..1u] =
            [::std::fmt::rt::Argument(::std::fmt::rt::Argument{position:
                                                                   ::std::fmt::rt::ArgumentNext,
                                                               format:
                                                                   ::std::fmt::rt::FormatSpec{fill:
                                                                                                  ' ',
                                                                                              align:
                                                                                                  ::std::fmt::parse::AlignUnknown,
                                                                                              flags:
                                                                                                  0u,
                                                                                              precision:
                                                                                                  ::std::fmt::parse::CountImplied,
                                                                                              width:
                                                                                                  ::std::fmt::parse::CountImplied,},
                                                               method:
                                                                   None,})];
        let __arg0 = &2;
        let args: &[::std::fmt::Argument] =
            &[::std::fmt::argument(::std::fmt::Default::fmt, __arg0)];
        let ret: ::std::fmt::Arguments =
            unsafe {
                ::std::cast::transmute((__static_fmtstr.as_slice(), args))
            };
        |test| { use_test(test) }(&ret)
    };
}

@alexcrichton
Copy link
Member Author

Changed my mind, instead of calling as_slice it now used ascription of a local variable to coerce the static format string into a slice.

unsafe { write_unsafe(output, args.fmt, args.args) }
}

/// The `write` function takes an output stream, a precompiled format string,
Copy link
Member

Choose a reason for hiding this comment

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

"The write_unsafe function"?

@huonw
Copy link
Member

huonw commented Sep 11, 2013

Ok; looks pretty good, some nits/questions here and there.

It'd be good to have negative tests for situations like format_args!("oops, I forgot the {}", "function"), format_args!("I can't remember {}", |_| {}, "the order") and format_args!().

I plan on transitioning the standard logging/failing to using these macros soon. This is currently blocking on inner statics being usable in cross-crate situations (still tracking down bugs there), but this will hopefully be coming soon!

Part of #9036?

Additionally, I'd rather settle on a name now than later, so if anyone has a better suggestion other than format_args, I'm not attached to the name at all :)

format_fn, with_format, format_with, parse_format, preparse_format, I don't know.

(Needs a rebase, btw.)

@alexcrichton
Copy link
Member Author

The compile-fail tests were a good idea, I was able to improve the span of the error message when you didn't supply a function (supplied a string instead).

I don't think that this is related to #9036 right now, but that still requires more investigation (will resume once this lands).

Hmm, I'm still a little torn on the name for this, so I suppose I'll just leave it as format_args for now, when thinking about it it's actually pretty easy to rename if the macro does the same thing.

@alexcrichton
Copy link
Member Author

(also re-pushed)

/// which is valid because the compiler performs all necessary validation to
/// ensure that the resulting call to format/write would be safe.
#[doc(hidden)] #[inline]
pub unsafe fn mkarguments<'a>(fmt: &'static [rt::Piece<'static>],
Copy link
Member

Choose a reason for hiding this comment

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

Why mkarguments rather than Arguments::new?

The purpose of this macro is to further reduce the number of allocations which
occur when dealing with formatting strings. This macro will perform all of the
static analysis necessary to validate that a format string is safe, and then it
will wrap up the "format string" into an opaque struct which can then be passed
around.

Two safe functions are added (write/format) which take this opaque argument
structure, unwrap it, and then call the unsafe version of write/format (in an
unsafe block). Other than these two functions, it is not intended for anyone to
ever look inside this opaque struct.

The macro looks a bit odd, but mostly because of rvalue lifetimes this is the
only way for it to be safe that I know of.

Example use-cases of this are:

* third-party libraries can use the default formatting syntax without any
  forced allocations
* the fail!() macro can avoid allocating the format string
* the logging macros can avoid allocation any strings
@alexcrichton
Copy link
Member Author

Thanks again for the feedback! updated again.

@huonw
Copy link
Member

huonw commented Sep 12, 2013

r+, but I wonder if we could just write everything in terms of format_args to reduce duplication? i.e. format!("", ...) => format_args!(::std::fmt::rt::format, "", ...) (this transformation would be best to be done as a syntax extension to get better error messages).

@alexcrichton
Copy link
Member Author

Hmm... that's actually not a bad idea. I will investigate this after landing. It would certainly simplify ext/ifmt.rs by a fair amount.

bors added a commit that referenced this pull request Sep 12, 2013
The purpose of this macro is to further reduce the number of allocations which
occur when dealing with formatting strings. This macro will perform all of the
static analysis necessary to validate that a format string is safe, and then it
will wrap up the "format string" into an opaque struct which can then be passed
around.

Two safe functions are added (write/format) which take this opaque argument
structure, unwrap it, and then call the unsafe version of write/format (in an
unsafe block). Other than these two functions, it is not intended for anyone to
ever look inside this opaque struct.

The macro looks a bit odd, but mostly because of rvalue lifetimes this is the
only way for it to be safe that I know of.

Example use-cases of this are:

* third-party libraries can use the default formatting syntax without any
  forced allocations
* the fail!() macro can avoid allocating the format string
* the logging macros can avoid allocation any strings

I plan on transitioning the standard logging/failing to using these macros soon. This is currently blocking on inner statics being usable in cross-crate situations (still tracking down bugs there), but this will hopefully be coming soon!

Additionally, I'd rather settle on a name now than later, so if anyone has a better suggestion other than `format_args`, I'm not attached to the name at all :)
@bors bors closed this Sep 12, 2013
bors added a commit that referenced this pull request Sep 15, 2013
This follows from the discussion in #9012.

* All macros are now defined in terms of `format_args!` allowing for removal of a good bit of code in the syntax extension
* The syntax extension is now in a more aptly-named file, `format.rs`
* Documentation was added for the `format!`-related macros.
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.

3 participants