Skip to content

Commit fbc04e6

Browse files
committed
Add the formatter_len_hint method to Show and use it in to_string
Implements `formatter_len_hint` for several simple types. Length of the formatted string is approximated for floats and integers.
1 parent e079ed7 commit fbc04e6

File tree

8 files changed

+191
-12
lines changed

8 files changed

+191
-12
lines changed

src/liballoc/boxed.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use core::default::Default;
1717
use core::fmt;
1818
use core::intrinsics;
1919
use core::mem;
20-
use core::option::Option;
20+
use core::option::{Option, Some};
2121
use core::raw::TraitObject;
2222
use core::result::{Ok, Err, Result};
2323

@@ -130,12 +130,22 @@ impl<T: fmt::Show> fmt::Show for Box<T> {
130130
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131131
(**self).fmt(f)
132132
}
133+
134+
#[inline]
135+
fn formatter_len_hint(&self) -> Option<uint> {
136+
(**self).formatter_len_hint()
137+
}
133138
}
134139

135140
impl fmt::Show for Box<Any+'static> {
136141
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137142
f.pad("Box<Any>")
138143
}
144+
145+
#[inline]
146+
fn formatter_len_hint(&self) -> Option<uint> {
147+
Some(8)
148+
}
139149
}
140150

141151
#[cfg(test)]

src/libcollections/string.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,11 @@ impl fmt::Show for String {
899899
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
900900
self.as_slice().fmt(f)
901901
}
902+
903+
#[inline]
904+
fn formatter_len_hint(&self) -> Option<uint> {
905+
self.as_slice().formatter_len_hint()
906+
}
902907
}
903908

904909
#[experimental = "waiting on Hash stabilization"]

src/libcore/fmt/mod.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414

1515
use any;
1616
use cell::{Cell, Ref, RefMut};
17+
use cmp;
1718
use collections::Collection;
19+
use f32;
1820
use iter::{Iterator, range};
1921
use kinds::Copy;
2022
use mem;
23+
use num::FPNormal;
2124
use option::{Option, Some, None};
2225
use ops::Deref;
2326
use result::{Ok, Err};
@@ -171,6 +174,11 @@ impl<'a> Show for Arguments<'a> {
171174
pub trait Show {
172175
/// Formats the value using the given formatter.
173176
fn fmt(&self, &mut Formatter) -> Result;
177+
178+
/// Returns a conservative estimate of the size of the formatted string.
179+
///
180+
/// A return of `None` means a close estimate is costly or not feasible.
181+
fn formatter_len_hint(&self) -> Option<uint> { None }
174182
}
175183

176184
/// Format trait for the `b` character
@@ -260,7 +268,7 @@ macro_rules! uniform_fn_call_workaround {
260268
pub fn $name<T: $trait_>(x: &T, fmt: &mut Formatter) -> Result {
261269
x.fmt(fmt)
262270
}
263-
)*
271+
)*
264272
}
265273
}
266274
uniform_fn_call_workaround! {
@@ -591,6 +599,7 @@ impl<'a, T: Show> Show for &'a mut T {
591599
}
592600
impl<'a> Show for &'a Show+'a {
593601
fn fmt(&self, f: &mut Formatter) -> Result { (*self).fmt(f) }
602+
fn formatter_len_hint(&self) -> Option<uint> { #![inline] (*self).formatter_len_hint() }
594603
}
595604

596605
impl Bool for bool {
@@ -705,20 +714,69 @@ macro_rules! floating(($ty:ident) => {
705714
floating!(f32)
706715
floating!(f64)
707716

717+
fn formatter_len_hint_string(this: &&str) -> Option<uint> {
718+
Some(this.len())
719+
}
720+
721+
fn formatter_len_hint_bool(_: &bool) -> Option<uint> {
722+
Some(5)
723+
}
724+
725+
fn formatter_len_hint_char(_: &char) -> Option<uint> {
726+
Some(4)
727+
}
728+
729+
static LOG10_2: f32 = f32::consts::LOG10_E / f32::consts::LOG2_E;
730+
static SHIFTED_LOG10_2: i32 = (LOG10_2 * (1u << 16) as f32) as i32;
731+
732+
fn formatter_len_hint_float_f32(this: &f32) -> Option<uint> {
733+
use num::{Float, Signed};
734+
735+
match this.classify() {
736+
FPNormal => {
737+
let (_, exponent, _) = this.integer_decode();
738+
// A fast approximate log_10. Add a small value at the end for the
739+
// sign and decimal separator.
740+
let log_10 = ((exponent as i32 + 23) * SHIFTED_LOG10_2) >> 16;
741+
Some(cmp::max(log_10, -5).abs() as uint + 4)
742+
}
743+
// Otherwise, "+inf" is the longest string we might print.
744+
_ => Some(4)
745+
}
746+
}
747+
748+
fn formatter_len_hint_float_f64(this: &f64) -> Option<uint> {
749+
use num::{Float, Signed};
750+
751+
match this.classify() {
752+
FPNormal => {
753+
let (_, exponent, _) = this.integer_decode();
754+
let log_10 = ((exponent as i32 + 52) * SHIFTED_LOG10_2) >> 16;
755+
Some(cmp::max(log_10, -5).abs() as uint + 4)
756+
}
757+
// Otherwise, "+inf" is the longest string we might print.
758+
_ => Some(4)
759+
}
760+
}
761+
708762
// Implementation of Show for various core types
709763

710-
macro_rules! delegate(($ty:ty to $other:ident) => {
764+
macro_rules! delegate(($ty:ty to $other:ident $($suffix:ident)*) => {
711765
impl<'a> Show for $ty {
712766
fn fmt(&self, f: &mut Formatter) -> Result {
713767
(concat_idents!(secret_, $other)(self, f))
714768
}
769+
#[inline]
770+
fn formatter_len_hint(&self) -> Option<uint> {
771+
(concat_idents!(formatter_len_hint_, $other, $($suffix),*)(self))
772+
}
715773
}
716774
})
717775
delegate!(&'a str to string)
718776
delegate!(bool to bool)
719777
delegate!(char to char)
720-
delegate!(f32 to float)
721-
delegate!(f64 to float)
778+
delegate!(f32 to float _f32)
779+
delegate!(f64 to float _f64)
722780

723781
impl<T> Show for *const T {
724782
fn fmt(&self, f: &mut Formatter) -> Result { secret_pointer(self, f) }
@@ -792,6 +850,11 @@ impl Show for () {
792850
fn fmt(&self, f: &mut Formatter) -> Result {
793851
f.pad("()")
794852
}
853+
854+
#[inline]
855+
fn formatter_len_hint(&self) -> Option<uint> {
856+
Some(2)
857+
}
795858
}
796859

797860
impl<T: Copy + Show> Show for Cell<T> {
@@ -804,12 +867,22 @@ impl<'b, T: Show> Show for Ref<'b, T> {
804867
fn fmt(&self, f: &mut Formatter) -> Result {
805868
(**self).fmt(f)
806869
}
870+
871+
#[inline]
872+
fn formatter_len_hint(&self) -> Option<uint> {
873+
(**self).formatter_len_hint()
874+
}
807875
}
808876

809877
impl<'b, T: Show> Show for RefMut<'b, T> {
810878
fn fmt(&self, f: &mut Formatter) -> Result {
811879
(*(self.deref())).fmt(f)
812880
}
881+
882+
#[inline]
883+
fn formatter_len_hint(&self) -> Option<uint> {
884+
(*(self.deref())).formatter_len_hint()
885+
}
813886
}
814887

815888
// If you expected tests to be here, look instead at the run-pass/ifmt.rs test,

src/libcore/fmt/num.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414

1515
#![allow(unsigned_negate)]
1616

17+
use clone::Clone;
1718
use collections::Collection;
1819
use fmt;
1920
use iter::DoubleEndedIterator;
20-
use num::{Int, cast, zero};
21+
use mem::size_of;
22+
use num::{Int, Signed, cast, zero};
23+
use option::{Option, Some};
2124
use slice::{ImmutableSlice, MutableSlice};
2225

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

152+
macro_rules! int_base_hint {
153+
($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr, $abs:ident) => {
154+
impl fmt::$Trait for $T {
155+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156+
$Radix.fmt_int(*self as $U, f)
157+
}
158+
159+
fn formatter_len_hint(&self) -> Option<uint> {
160+
let num = self.$abs();
161+
let width = size_of::<$T>() * 8;
162+
// Approximate log_2 of the target base.
163+
let log2base = 1 << $log2log2base;
164+
165+
// Get the number of digits in the target base.
166+
let binary_digits = width - (num | log2base as $T).leading_zeros();
167+
Some(binary_digits / log2base)
168+
}
169+
}
170+
};
171+
// Use `clone` on uints as a noop method in place of abs.
172+
($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr) => {
173+
int_base_hint!($Trait for $T as $U -> $Radix; $log2log2base, clone)
174+
}
175+
}
149176
macro_rules! radix_fmt {
150-
($T:ty as $U:ty, $fmt:ident) => {
177+
($T:ty as $U:ty, $fmt:ident, $abs:ident) => {
151178
impl fmt::Show for RadixFmt<$T, Radix> {
152179
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153-
match *self { RadixFmt(ref x, radix) => radix.$fmt(*x as $U, f) }
180+
let &RadixFmt(x, radix) = self;
181+
radix.$fmt(x as $U, f)
182+
}
183+
184+
fn formatter_len_hint(&self) -> Option<uint> {
185+
let &RadixFmt(num, radix) = self;
186+
let num = num.$abs();
187+
let width = size_of::<$T>() * 8;
188+
// Approximate log_2 of the target base.
189+
let log2base = 7 - radix.base().leading_zeros();
190+
191+
// Get the number of digits in the target base.
192+
let binary_digits = width - (num | log2base as $T).leading_zeros();
193+
Some(binary_digits / log2base + 1)
154194
}
155195
}
196+
};
197+
// Use `clone` on uints as a noop method in place of abs.
198+
($T:ty as $U:ty, $fmt:ident) => {
199+
radix_fmt!($T as $U, $fmt, clone)
156200
}
157201
}
158202
macro_rules! int_base {
@@ -166,20 +210,20 @@ macro_rules! int_base {
166210
}
167211
macro_rules! integer {
168212
($Int:ident, $Uint:ident) => {
169-
int_base!(Show for $Int as $Int -> Decimal)
170213
int_base!(Signed for $Int as $Int -> Decimal)
171214
int_base!(Binary for $Int as $Uint -> Binary)
172215
int_base!(Octal for $Int as $Uint -> Octal)
173216
int_base!(LowerHex for $Int as $Uint -> LowerHex)
174217
int_base!(UpperHex for $Int as $Uint -> UpperHex)
175-
radix_fmt!($Int as $Int, fmt_int)
218+
int_base_hint!(Show for $Int as $Int -> Decimal; 1, abs)
219+
radix_fmt!($Int as $Int, fmt_int, abs)
176220

177-
int_base!(Show for $Uint as $Uint -> Decimal)
178221
int_base!(Unsigned for $Uint as $Uint -> Decimal)
179222
int_base!(Binary for $Uint as $Uint -> Binary)
180223
int_base!(Octal for $Uint as $Uint -> Octal)
181224
int_base!(LowerHex for $Uint as $Uint -> LowerHex)
182225
int_base!(UpperHex for $Uint as $Uint -> UpperHex)
226+
int_base_hint!(Show for $Uint as $Uint -> Decimal; 1)
183227
radix_fmt!($Uint as $Uint, fmt_int)
184228
}
185229
}

src/libcoretest/fmt/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use core::fmt::Show;
12+
1113
mod num;
1214

1315
#[test]
@@ -16,3 +18,35 @@ fn test_format_flags() {
1618
let p = "".as_ptr();
1719
assert_eq!(format!("{:p} {:x}", p, 16u), format!("{:p} 10", p));
1820
}
21+
22+
#[test]
23+
fn test_len_hints_float() {
24+
let mut f = 5.0f32;
25+
for _ in range(0u, 30) {
26+
let s = format!("{}", f);
27+
let len = f.formatter_len_hint().unwrap();
28+
assert!(len >= s.len());
29+
assert!(len <= 128);
30+
f /= 10.0;
31+
}
32+
let mut f = 5.0f32;
33+
for _ in range(0u, 30) {
34+
let s = format!("{}", f);
35+
let len = f.formatter_len_hint().unwrap();
36+
assert!(len >= s.len());
37+
assert!(len <= 128);
38+
f *= 10.0;
39+
}
40+
}
41+
42+
#[test]
43+
fn test_len_hints_u64() {
44+
let mut f = 1u64;
45+
for _ in range(0u, 20) {
46+
let s = format!("{}", f);
47+
let len = f.formatter_len_hint().unwrap();
48+
assert!(len >= s.len());
49+
assert!(len <= 128);
50+
f *= 10;
51+
}
52+
}

src/librustrt/c_str.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ impl fmt::Show for CString {
315315
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
316316
String::from_utf8_lossy(self.as_bytes_no_nul()).fmt(f)
317317
}
318+
319+
#[inline]
320+
fn formatter_len_hint(&self) -> Option<uint> {
321+
Some(self.len())
322+
}
318323
}
319324

320325
/// A generic trait for converting a value to a CString.

src/libstd/ascii.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ impl<'a> fmt::Show for Ascii {
182182
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183183
(self.chr as char).fmt(f)
184184
}
185+
186+
fn formatter_len_hint(&self) -> Option<uint> {
187+
Some(1)
188+
}
185189
}
186190

187191
/// Trait for converting into an ascii type.

src/libstd/to_string.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ The `ToString` trait for converting to strings
1616

1717
#![experimental]
1818

19+
use io::Writer;
20+
use io;
1921
use fmt;
2022
use string::String;
2123

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

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

0 commit comments

Comments
 (0)