Skip to content

Commit

Permalink
Implement a format_args!() macro
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alexcrichton committed Sep 12, 2013
1 parent 6216661 commit 9a5f95a
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 82 deletions.
91 changes: 75 additions & 16 deletions src/libstd/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Some examples of the `format!` extension are:
format!("Hello") // => ~"Hello"
format!("Hello, {:s}!", "world") // => ~"Hello, world!"
format!("The number is {:d}", 1) // => ~"The number is 1"
format!("{}", ~[3, 4]) // => ~"~[3, 4]"
format!("{:?}", ~[3, 4]) // => ~"~[3, 4]"
format!("{value}", value=4) // => ~"4"
format!("{} {}", 1, 2) // => ~"1 2"
~~~
Expand Down Expand Up @@ -363,6 +363,32 @@ pub struct Argument<'self> {
priv value: &'self util::Void,
}

impl<'self> Arguments<'self> {
/// When using the format_args!() macro, this function is used to generate the
/// Arguments structure. The compiler inserts an `unsafe` block to call this,
/// 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 new<'a>(fmt: &'static [rt::Piece<'static>],
args: &'a [Argument<'a>]) -> Arguments<'a> {
Arguments{ fmt: cast::transmute(fmt), args: args }
}
}

/// This structure represents a safely precompiled version of a format string
/// and its arguments. This cannot be generated at runtime because it cannot
/// safely be done so, so no constructors are given and the fields are private
/// to prevent modification.
///
/// The `format_args!` macro will safely create an instance of this structure
/// and pass it to a user-supplied function. The macro validates the format
/// string at compile-time so usage of the `write` and `format` functions can
/// be safely performed.
pub struct Arguments<'self> {
priv fmt: &'self [rt::Piece<'self>],
priv args: &'self [Argument<'self>],
}

/// When a format is not otherwise specified, types are formatted by ascribing
/// to this trait. There is not an explicit way of selecting this trait to be
/// used for formatting, it is only if no other format is specified.
Expand Down Expand Up @@ -410,6 +436,26 @@ pub trait Float { fn fmt(&Self, &mut Formatter); }
/// and a list of arguments. The arguments will be formatted according to the
/// specified format string into the output stream provided.
///
/// # Arguments
///
/// * output - the buffer to write output to
/// * args - the precompiled arguments generated by `format_args!`
///
/// # Example
///
/// ~~~{.rust}
/// use std::fmt;
/// let w: &mut io::Writer = ...;
/// format_args!(|args| { fmt::write(w, args) }, "Hello, {}!", "world");
/// ~~~
pub fn write(output: &mut io::Writer, args: &Arguments) {
unsafe { write_unsafe(output, args.fmt, args.args) }
}

/// The `write_unsafe` function takes an output stream, a precompiled format
/// string, and a list of arguments. The arguments will be formatted according
/// to the specified format string into the output stream provided.
///
/// See the documentation for `format` for why this function is unsafe and care
/// should be taken if calling it manually.
///
Expand All @@ -426,8 +472,9 @@ pub trait Float { fn fmt(&Self, &mut Formatter); }
///
/// Note that this function assumes that there are enough arguments for the
/// format string.
pub unsafe fn write(output: &mut io::Writer,
fmt: &[rt::Piece], args: &[Argument]) {
pub unsafe fn write_unsafe(output: &mut io::Writer,
fmt: &[rt::Piece],
args: &[Argument]) {
let mut formatter = Formatter {
flags: 0,
width: None,
Expand All @@ -446,6 +493,25 @@ pub unsafe fn write(output: &mut io::Writer,
/// The format function takes a precompiled format string and a list of
/// arguments, to return the resulting formatted string.
///
/// # Arguments
///
/// * args - a structure of arguments generated via the `format_args!` macro.
/// Because this structure can only be safely generated at
/// compile-time, this function is safe.
///
/// # Example
///
/// ~~~{.rust}
/// use std::fmt;
/// let s = format_args!(fmt::format, "Hello, {}!", "world");
/// assert_eq!(s, "Hello, world!");
/// ~~~
pub fn format(args: &Arguments) -> ~str {
unsafe { format_unsafe(args.fmt, args.args) }
}

/// The unsafe version of the formatting function.
///
/// This is currently an unsafe function because the types of all arguments
/// aren't verified by immediate callers of this function. This currently does
/// not validate that the correct types of arguments are specified for each
Expand All @@ -465,9 +531,9 @@ pub unsafe fn write(output: &mut io::Writer,
///
/// Note that this function assumes that there are enough arguments for the
/// format string.
pub unsafe fn format(fmt: &[rt::Piece], args: &[Argument]) -> ~str {
pub unsafe fn format_unsafe(fmt: &[rt::Piece], args: &[Argument]) -> ~str {
let mut output = MemWriter::new();
write(&mut output as &mut io::Writer, fmt, args);
write_unsafe(&mut output as &mut io::Writer, fmt, args);
return str::from_utf8_owned(output.inner());
}

Expand Down Expand Up @@ -740,7 +806,7 @@ impl<'self> Formatter<'self> {

/// This is a function which calls are emitted to by the compiler itself to
/// create the Argument structures that are passed into the `format` function.
#[doc(hidden)]
#[doc(hidden)] #[inline]
pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter),
t: &'a T) -> Argument<'a> {
unsafe {
Expand All @@ -753,14 +819,14 @@ pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter),

/// When the compiler determines that the type of an argument *must* be a string
/// (such as for select), then it invokes this method.
#[doc(hidden)]
#[doc(hidden)] #[inline]
pub fn argumentstr<'a>(s: &'a &str) -> Argument<'a> {
argument(String::fmt, s)
}

/// When the compiler determines that the type of an argument *must* be a uint
/// (such as for plural), then it invokes this method.
#[doc(hidden)]
#[doc(hidden)] #[inline]
pub fn argumentuint<'a>(s: &'a uint) -> Argument<'a> {
argument(Unsigned::fmt, s)
}
Expand Down Expand Up @@ -899,14 +965,8 @@ impl<T> Pointer for *T {
}
}
}

impl<T> Pointer for *mut T {
fn fmt(t: &*mut T, f: &mut Formatter) {
f.flags |= 1 << (parse::FlagAlternate as uint);
do ::uint::to_str_bytes(*t as uint, 16) |buf| {
f.pad_integral(buf, "0x", true);
}
}
fn fmt(t: &*mut T, f: &mut Formatter) { Pointer::fmt(&(*t as *T), f) }
}

// Implementation of Default for various core types
Expand Down Expand Up @@ -940,7 +1000,6 @@ delegate!(f64 to Float)
impl<T> Default for *T {
fn fmt(me: &*T, f: &mut Formatter) { Pointer::fmt(me, f) }
}

impl<T> Default for *mut T {
fn fmt(me: &*mut T, f: &mut Formatter) { Pointer::fmt(me, f) }
}
Expand Down
2 changes: 2 additions & 0 deletions src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub fn syntax_expander_table() -> SyntaxEnv {
builtin_normal_tt_no_ctxt(ext::ifmt::expand_write));
syntax_expanders.insert(intern(&"writeln"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_writeln));
syntax_expanders.insert(intern(&"format_args"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_format_args));
syntax_expanders.insert(
intern(&"auto_encode"),
@SE(ItemDecorator(ext::auto_encode::expand_auto_encode)));
Expand Down
Loading

5 comments on commit 9a5f95a

@bors
Copy link
Contributor

@bors bors commented on 9a5f95a Sep 12, 2013

Choose a reason for hiding this comment

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

saw approval from huonw
at alexcrichton@9a5f95a

@bors
Copy link
Contributor

@bors bors commented on 9a5f95a Sep 12, 2013

Choose a reason for hiding this comment

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

merging alexcrichton/rust/format-args = 9a5f95a into auto

@bors
Copy link
Contributor

@bors bors commented on 9a5f95a Sep 12, 2013

Choose a reason for hiding this comment

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

alexcrichton/rust/format-args = 9a5f95a merged ok, testing candidate = 4825db4

@bors
Copy link
Contributor

@bors bors commented on 9a5f95a Sep 12, 2013

@bors
Copy link
Contributor

@bors bors commented on 9a5f95a Sep 12, 2013

Choose a reason for hiding this comment

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

fast-forwarding master to auto = 4825db4

Please sign in to comment.