Skip to content

Commit 99b2e6d

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 2b0d73b commit 99b2e6d

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

src/encode.rs

+39-3
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,12 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> {
398398
/// assert_eq!("he11owor1d\0ld", output);
399399
/// # Ok::<(), bs58::encode::Error>(())
400400
/// ```
401-
pub fn onto(self, mut output: impl EncodeTarget) -> Result<usize> {
401+
pub fn onto(self, output: impl EncodeTarget) -> Result<usize> {
402+
self.ref_onto(output)
403+
}
404+
405+
/// Same as [`Self::onto`] but does not consume `self`.
406+
fn ref_onto(&self, mut output: impl EncodeTarget) -> Result<usize> {
402407
let input = self.input.as_ref();
403408
match self.check {
404409
Check::Disabled => output.encode_with(max_encoded_len(input.len()), |output| {
@@ -422,11 +427,42 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> {
422427
}
423428
}
424429

430+
#[cfg(feature = "alloc")]
431+
impl<'a, I: AsRef<[u8]>> core::fmt::Display for EncodeBuilder<'a, I> {
432+
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
433+
// If input is short enough, encode it into a buffer on stack to avoid
434+
// allocation. 96 bytes length limit should be plenty and cover all
435+
// sane cases; base58 is typically used for things like encryption keys
436+
// and hashes which are often no more than 32-bytes long.
437+
const LEN_LIMIT: usize = 96;
438+
439+
#[cfg(any(feature = "check", feature = "cb58"))]
440+
const MAX_LEN: usize = LEN_LIMIT + CHECKSUM_LEN + 1;
441+
#[cfg(not(any(feature = "check", feature = "cb58")))]
442+
const MAX_LEN: usize = LEN_LIMIT;
443+
let mut buf = [0u8; max_encoded_len(MAX_LEN)];
444+
let mut vec = Vec::new();
445+
446+
let output = if self.input.as_ref().len() <= LEN_LIMIT {
447+
let len = self.ref_onto(&mut buf[..]).unwrap();
448+
&buf[..len]
449+
} else {
450+
self.ref_onto(&mut vec).unwrap();
451+
vec.as_slice()
452+
};
453+
454+
// SAFETY: we know that alphabet can only include ASCII characters
455+
// thus our result is an ASCII string.
456+
#[allow(unsafe_code)]
457+
fmt.write_str(unsafe { std::str::from_utf8_unchecked(output) })
458+
}
459+
}
460+
425461
/// Return maximum possible encoded length of a buffer with given length.
426462
///
427463
/// Assumes that the `len` already includes version and checksum bytes if those
428-
/// are
429-
fn max_encoded_len(len: usize) -> usize {
464+
/// are part of the encoding.
465+
const fn max_encoded_len(len: usize) -> usize {
430466
// log_2(256) / log_2(58) ≈ 1.37. Assume 1.5 for easier calculation.
431467
len + (len + 1) / 2
432468
}

tests/encode.rs

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ fn test_encode() {
7777
assert_eq!((PREFIX, s.as_bytes()), vec.split_at(3));
7878
}
7979
}
80+
81+
// Test Display implementation
82+
assert_eq!(s, format!("{}", bs58::encode(val)));
83+
assert_eq!(s, bs58::encode(val).to_string());
8084
}
8185
}
8286

0 commit comments

Comments
 (0)