Skip to content

Commit 4738b40

Browse files
committed
encode: implement Display for EncodeBuilder
Add implementation of core::fmt::Display for EncodeBuilder which tries to avoid memory allocations when encoding sensibly small values (specifically, shorter than 96 bytes).
1 parent 9785331 commit 4738b40

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

src/encode.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,12 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> {
328328
/// assert_eq!("he11owor1d\0ld", output);
329329
/// # Ok::<(), bs58::encode::Error>(())
330330
/// ```
331-
pub fn into(self, mut output: impl EncodeTarget) -> Result<usize> {
331+
pub fn into(self, output: impl EncodeTarget) -> Result<usize> {
332+
self.to(output)
333+
}
334+
335+
/// Same as [`Self::into`] but does not consume `self`.
336+
fn to(&self, mut output: impl EncodeTarget) -> Result<usize> {
332337
let input = self.input.as_ref();
333338
match self.check {
334339
Check::Disabled => output.encode_with(max_encoded_len(input.len()), |output| {
@@ -352,11 +357,41 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> {
352357
}
353358
}
354359

360+
#[cfg(feature = "alloc")]
361+
impl<'a, I: AsRef<[u8]>> core::fmt::Display for EncodeBuilder<'a, I> {
362+
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
363+
// If input is short enough, encode it into a buffer on stack to avoid
364+
// allocation. 96 bytes length limit should be plenty and cover all
365+
// sane cases; base58 is typically used for things like encryption keys
366+
// and hashes which are often no more than 32-bytes long.
367+
const LEN_LIMIT: usize = 96;
368+
369+
#[cfg(any(feature = "check", feature = "cb58"))]
370+
const MAX_LEN: usize = LEN_LIMIT + CHECKSUM_LEN + 1;
371+
#[cfg(not(any(feature = "check", feature = "cb58")))]
372+
const MAX_LEN: usize = LEN_LIMIT;
373+
let mut buf = [0u8; max_encoded_len(MAX_LEN)];
374+
let mut vec = Vec::new();
375+
376+
let output = if self.input.as_ref().len() <= LEN_LIMIT {
377+
let len = self.to(&mut buf[..]).unwrap();
378+
&buf[..len]
379+
} else {
380+
self.to(&mut vec).unwrap();
381+
vec.as_slice()
382+
};
383+
384+
// SAFETY: we know that alphabet can only include ASCII characters
385+
// thus our result is an ASCII string.
386+
fmt.write_str(unsafe { std::str::from_utf8_unchecked(output) })
387+
}
388+
}
389+
355390
/// Return maximum possible encoded length of a buffer with given length.
356391
///
357392
/// Assumes that the `len` already includes version and checksum bytes if those
358-
/// are
359-
fn max_encoded_len(len: usize) -> usize {
393+
/// are part of the encoding.
394+
const fn max_encoded_len(len: usize) -> usize {
360395
// log_2(256) / log_2(58) ≈ 1.37. Assume 1.5 for easier calculation.
361396
len + (len + 1) / 2
362397
}

tests/encode.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ fn test_encode() {
2929
}
3030
assert_eq!(&FILLER[(s.len() + 1)..], &bytes[(s.len() + 1)..]);
3131
}
32+
33+
// Test Display implementation
34+
assert_eq!(s, format!("{}", bs58::encode(val)));
35+
assert_eq!(s, bs58::encode(val).to_string());
3236
}
3337
}
3438

0 commit comments

Comments
 (0)