Skip to content

Add the formatter_len_hint method to Show and use it in to_string #16544

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/liballoc/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use core::default::Default;
use core::fmt;
use core::intrinsics;
use core::mem;
use core::option::Option;
use core::option::{Option, Some};
use core::raw::TraitObject;
use core::result::{Ok, Err, Result};

Expand Down Expand Up @@ -130,12 +130,22 @@ impl<T: fmt::Show> fmt::Show for Box<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(**self).fmt(f)
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
(**self).formatter_len_hint()
}
}

impl fmt::Show for Box<Any+'static> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad("Box<Any>")
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
Some(8)
}
}

#[cfg(test)]
Expand Down
5 changes: 5 additions & 0 deletions src/libcollections/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,11 @@ impl fmt::Show for String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_slice().fmt(f)
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
self.as_slice().formatter_len_hint()
}
}

#[experimental = "waiting on Hash stabilization"]
Expand Down
81 changes: 77 additions & 4 deletions src/libcore/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@

use any;
use cell::{Cell, Ref, RefMut};
use cmp;
use collections::Collection;
use f32;
use iter::{Iterator, range};
use kinds::Copy;
use mem;
use num::FPNormal;
use option::{Option, Some, None};
use ops::Deref;
use result::{Ok, Err};
Expand Down Expand Up @@ -171,6 +174,11 @@ impl<'a> Show for Arguments<'a> {
pub trait Show {
/// Formats the value using the given formatter.
fn fmt(&self, &mut Formatter) -> Result;

/// Returns a conservative estimate of the size of the formatted string.
///
/// A return of `None` means a close estimate is costly or not feasible.
fn formatter_len_hint(&self) -> Option<uint> { None }
}

/// Format trait for the `b` character
Expand Down Expand Up @@ -260,7 +268,7 @@ macro_rules! uniform_fn_call_workaround {
pub fn $name<T: $trait_>(x: &T, fmt: &mut Formatter) -> Result {
x.fmt(fmt)
}
)*
)*
}
}
uniform_fn_call_workaround! {
Expand Down Expand Up @@ -591,6 +599,7 @@ impl<'a, T: Show> Show for &'a mut T {
}
impl<'a> Show for &'a Show+'a {
fn fmt(&self, f: &mut Formatter) -> Result { (*self).fmt(f) }
fn formatter_len_hint(&self) -> Option<uint> { #![inline] (*self).formatter_len_hint() }
}

impl Bool for bool {
Expand Down Expand Up @@ -705,20 +714,69 @@ macro_rules! floating(($ty:ident) => {
floating!(f32)
floating!(f64)

fn formatter_len_hint_string(this: &&str) -> Option<uint> {
Some(this.len())
}

fn formatter_len_hint_bool(_: &bool) -> Option<uint> {
Some(5)
}

fn formatter_len_hint_char(_: &char) -> Option<uint> {
Some(4)
}

static LOG10_2: f32 = f32::consts::LOG10_E / f32::consts::LOG2_E;
static SHIFTED_LOG10_2: i32 = (LOG10_2 * (1u << 16) as f32) as i32;

fn formatter_len_hint_float_f32(this: &f32) -> Option<uint> {
use num::{Float, Signed};

match this.classify() {
FPNormal => {
let (_, exponent, _) = this.integer_decode();
// A fast approximate log_10. Add a small value at the end for the
// sign and decimal separator.
let log_10 = ((exponent as i32 + 23) * SHIFTED_LOG10_2) >> 16;
Some(cmp::max(log_10, -5).abs() as uint + 4)
}
// Otherwise, "+inf" is the longest string we might print.
_ => Some(4)
}
}

fn formatter_len_hint_float_f64(this: &f64) -> Option<uint> {
use num::{Float, Signed};

match this.classify() {
FPNormal => {
let (_, exponent, _) = this.integer_decode();
let log_10 = ((exponent as i32 + 52) * SHIFTED_LOG10_2) >> 16;
Some(cmp::max(log_10, -5).abs() as uint + 4)
}
// Otherwise, "+inf" is the longest string we might print.
_ => Some(4)
}
}

// Implementation of Show for various core types

macro_rules! delegate(($ty:ty to $other:ident) => {
macro_rules! delegate(($ty:ty to $other:ident $($suffix:ident)*) => {
impl<'a> Show for $ty {
fn fmt(&self, f: &mut Formatter) -> Result {
(concat_idents!(secret_, $other)(self, f))
}
#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
(concat_idents!(formatter_len_hint_, $other, $($suffix),*)(self))
}
}
})
delegate!(&'a str to string)
delegate!(bool to bool)
delegate!(char to char)
delegate!(f32 to float)
delegate!(f64 to float)
delegate!(f32 to float _f32)
delegate!(f64 to float _f64)

impl<T> Show for *const T {
fn fmt(&self, f: &mut Formatter) -> Result { secret_pointer(self, f) }
Expand Down Expand Up @@ -792,6 +850,11 @@ impl Show for () {
fn fmt(&self, f: &mut Formatter) -> Result {
f.pad("()")
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
Some(2)
}
}

impl<T: Copy + Show> Show for Cell<T> {
Expand All @@ -804,12 +867,22 @@ impl<'b, T: Show> Show for Ref<'b, T> {
fn fmt(&self, f: &mut Formatter) -> Result {
(**self).fmt(f)
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
(**self).formatter_len_hint()
}
}

impl<'b, T: Show> Show for RefMut<'b, T> {
fn fmt(&self, f: &mut Formatter) -> Result {
(*(self.deref())).fmt(f)
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
(*(self.deref())).formatter_len_hint()
}
}

// If you expected tests to be here, look instead at the run-pass/ifmt.rs test,
Expand Down
56 changes: 50 additions & 6 deletions src/libcore/fmt/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@

#![allow(unsigned_negate)]

use clone::Clone;
use collections::Collection;
use fmt;
use iter::DoubleEndedIterator;
use num::{Int, cast, zero};
use mem::size_of;
use num::{Int, Signed, cast, zero};
use option::{Option, Some};
use slice::{ImmutableSlice, MutableSlice};

/// A type that represents a specific radix
Expand Down Expand Up @@ -146,13 +149,54 @@ pub fn radix<T>(x: T, base: u8) -> RadixFmt<T, Radix> {
RadixFmt(x, Radix::new(base))
}

macro_rules! int_base_hint {
($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr, $abs:ident) => {
impl fmt::$Trait for $T {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
$Radix.fmt_int(*self as $U, f)
}

fn formatter_len_hint(&self) -> Option<uint> {
let num = self.$abs();
let width = size_of::<$T>() * 8;
// Approximate log_2 of the target base.
let log2base = 1 << $log2log2base;

// Get the number of digits in the target base.
let binary_digits = width - (num | log2base as $T).leading_zeros();
Some(binary_digits / log2base)
}
}
};
// Use `clone` on uints as a noop method in place of abs.
($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr) => {
int_base_hint!($Trait for $T as $U -> $Radix; $log2log2base, clone)
}
}
macro_rules! radix_fmt {
($T:ty as $U:ty, $fmt:ident) => {
($T:ty as $U:ty, $fmt:ident, $abs:ident) => {
impl fmt::Show for RadixFmt<$T, Radix> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { RadixFmt(ref x, radix) => radix.$fmt(*x as $U, f) }
let &RadixFmt(x, radix) = self;
radix.$fmt(x as $U, f)
}

fn formatter_len_hint(&self) -> Option<uint> {
let &RadixFmt(num, radix) = self;
let num = num.$abs();
let width = size_of::<$T>() * 8;
// Approximate log_2 of the target base.
let log2base = 7 - radix.base().leading_zeros();

// Get the number of digits in the target base.
let binary_digits = width - (num | log2base as $T).leading_zeros();
Some(binary_digits / log2base + 1)
}
}
};
// Use `clone` on uints as a noop method in place of abs.
($T:ty as $U:ty, $fmt:ident) => {
radix_fmt!($T as $U, $fmt, clone)
}
}
macro_rules! int_base {
Expand All @@ -166,20 +210,20 @@ macro_rules! int_base {
}
macro_rules! integer {
($Int:ident, $Uint:ident) => {
int_base!(Show for $Int as $Int -> Decimal)
int_base!(Signed for $Int as $Int -> Decimal)
int_base!(Binary for $Int as $Uint -> Binary)
int_base!(Octal for $Int as $Uint -> Octal)
int_base!(LowerHex for $Int as $Uint -> LowerHex)
int_base!(UpperHex for $Int as $Uint -> UpperHex)
radix_fmt!($Int as $Int, fmt_int)
int_base_hint!(Show for $Int as $Int -> Decimal; 1, abs)
radix_fmt!($Int as $Int, fmt_int, abs)

int_base!(Show for $Uint as $Uint -> Decimal)
int_base!(Unsigned for $Uint as $Uint -> Decimal)
int_base!(Binary for $Uint as $Uint -> Binary)
int_base!(Octal for $Uint as $Uint -> Octal)
int_base!(LowerHex for $Uint as $Uint -> LowerHex)
int_base!(UpperHex for $Uint as $Uint -> UpperHex)
int_base_hint!(Show for $Uint as $Uint -> Decimal; 1)
radix_fmt!($Uint as $Uint, fmt_int)
}
}
Expand Down
34 changes: 34 additions & 0 deletions src/libcoretest/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::fmt::Show;

mod num;

#[test]
Expand All @@ -16,3 +18,35 @@ fn test_format_flags() {
let p = "".as_ptr();
assert_eq!(format!("{:p} {:x}", p, 16u), format!("{:p} 10", p));
}

#[test]
fn test_len_hints_float() {
let mut f = 5.0f32;
for _ in range(0u, 30) {
let s = format!("{}", f);
let len = f.formatter_len_hint().unwrap();
assert!(len >= s.len());
assert!(len <= 128);
f /= 10.0;
}
let mut f = 5.0f32;
for _ in range(0u, 30) {
let s = format!("{}", f);
let len = f.formatter_len_hint().unwrap();
assert!(len >= s.len());
assert!(len <= 128);
f *= 10.0;
}
}

#[test]
fn test_len_hints_u64() {
let mut f = 1u64;
for _ in range(0u, 20) {
let s = format!("{}", f);
let len = f.formatter_len_hint().unwrap();
assert!(len >= s.len());
assert!(len <= 128);
f *= 10;
}
}
5 changes: 5 additions & 0 deletions src/librustrt/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ impl fmt::Show for CString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
String::from_utf8_lossy(self.as_bytes_no_nul()).fmt(f)
}

#[inline]
fn formatter_len_hint(&self) -> Option<uint> {
Some(self.len())
}
}

/// A generic trait for converting a value to a CString.
Expand Down
4 changes: 4 additions & 0 deletions src/libstd/ascii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ impl<'a> fmt::Show for Ascii {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self.chr as char).fmt(f)
}

fn formatter_len_hint(&self) -> Option<uint> {
Some(1)
}
}

/// Trait for converting into an ascii type.
Expand Down
6 changes: 5 additions & 1 deletion src/libstd/to_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The `ToString` trait for converting to strings

#![experimental]

use io::Writer;
use io;
use fmt;
use string::String;

Expand All @@ -33,7 +35,9 @@ pub trait IntoStr {

impl<T: fmt::Show> ToString for T {
fn to_string(&self) -> String {
format!("{}", *self)
let mut output = io::MemWriter::with_capacity(self.formatter_len_hint().unwrap_or(128));
let _ = write!(&mut output, "{}", *self);
String::from_utf8(output.unwrap()).unwrap()
}
}

Expand Down