Skip to content

Commit 2b89d5f

Browse files
committed
encode: implement Encodable and Decodable for Vec<T>
Right now we have explicit impls of Vec<T> for a ton of specific types T. This is annoying for users, who are unable to implement Vec<T> themselves for their own types. In particular is super annoying for me trying to do a semver trick with elements 0.25 and 0.26, since I can't reexport the 0.26 Encodable trait since I would need to implement it for Vec<TxOut> for the 0.25 TxOut type. (But conversely, if I retain the full trait definition in 0.25, I can't blanket-impl this trait for all T: Encodable because of this Vec<T> crap; so I wind up needing to explicitly implement Encodable and Decodeable for every single type I re-export, and if I forget any then that's a semver violation. Brutal.) We do this is because we need a specialized impl for Vec<u8> and Rust has no specialization. However, Kixunil pointed out on rust-bitcoin that we can just do "runtime specialization" by checking TypeId::of::<T> and doing the efficient thing when T is u8. In practice, because the compiler knows T at codegen time, it will know how the check goes and simply delete the check and the untaken branch, so we have exactly the same result as we would if we had specialization. This works only for T: 'static, but it turns out that all of our types that we currently impl Encodable for Vec<T> on are 'static (and it's hard to imagine one that wouldn't be, since all bitcoin/elements primitive types are simple PODs). It also seems plausible that a user could construct a Vec<u8> which the compiler somehow doesn't realize is a Vec<u8> (e.g. if they had a Vec<dyn Encodable> or something, in some future Rust version that supports that). Such code will be sound and correct, but be slow because it won't use the fast path. But I don't think this is a practical concern because I can't even figure out how to do it..
1 parent 45a6938 commit 2b89d5f

File tree

1 file changed

+73
-69
lines changed

1 file changed

+73
-69
lines changed

src/encode.rs

Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@
1616
//!
1717
1818
use std::io::Cursor;
19-
use std::{error, fmt, io, mem};
19+
use std::{any, error, fmt, io, mem};
2020

2121
use bitcoin::ScriptBuf;
2222
use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak};
2323

2424
use crate::hashes::{sha256, Hash};
2525
use crate::pset;
26-
use crate::transaction::{Transaction, TxIn, TxOut};
2726

2827
pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE};
2928

@@ -314,47 +313,84 @@ impl Decodable for bitcoin::hashes::sha256d::Hash {
314313
}
315314

316315
// Vectors
317-
macro_rules! impl_vec {
318-
($type: ty) => {
319-
impl Encodable for Vec<$type> {
320-
#[inline]
321-
fn consensus_encode<S: io::Write>(&self, mut s: S) -> Result<usize, Error> {
322-
let mut len = 0;
323-
len += VarInt(self.len() as u64).consensus_encode(&mut s)?;
324-
for c in self.iter() {
325-
len += c.consensus_encode(&mut s)?;
326-
}
327-
Ok(len)
316+
impl<T: Encodable + any::Any> Encodable for [T] {
317+
#[inline]
318+
fn consensus_encode<S: io::Write>(&self, mut s: S) -> Result<usize, Error> {
319+
if any::TypeId::of::<T>() == any::TypeId::of::<u8>() {
320+
// SAFETY: checked that T is exactly u8, so &self, of type, &[T], is exactly &[u8]
321+
let u8_slice = unsafe {
322+
std::slice::from_raw_parts(self.as_ptr().cast::<u8>(), self.len())
323+
};
324+
consensus_encode_with_size(u8_slice, s)
325+
} else {
326+
let mut len = 0;
327+
len += VarInt(self.len() as u64).consensus_encode(&mut s)?;
328+
for c in self {
329+
len += c.consensus_encode(&mut s)?;
328330
}
331+
Ok(len)
329332
}
333+
}
334+
}
330335

331-
impl Decodable for Vec<$type> {
332-
#[inline]
333-
fn consensus_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
334-
let len = VarInt::consensus_decode(&mut d)?.0;
335-
let byte_size = (len as usize)
336-
.checked_mul(mem::size_of::<$type>())
337-
.ok_or(self::Error::ParseFailed("Invalid length"))?;
338-
if byte_size > MAX_VEC_SIZE {
339-
return Err(self::Error::OversizedVectorAllocation {
340-
requested: byte_size,
341-
max: MAX_VEC_SIZE,
342-
});
343-
}
344-
let mut ret = Vec::with_capacity(len as usize);
345-
for _ in 0..len {
346-
ret.push(Decodable::consensus_decode(&mut d)?);
347-
}
348-
Ok(ret)
336+
impl<T: Encodable + any::Any> Encodable for Vec<T> {
337+
#[inline]
338+
fn consensus_encode<S: io::Write>(&self, s: S) -> Result<usize, Error> {
339+
self[..].consensus_encode(s)
340+
}
341+
}
342+
343+
impl<T: Encodable + any::Any> Encodable for Box<[T]> {
344+
#[inline]
345+
fn consensus_encode<S: io::Write>(&self, s: S) -> Result<usize, Error> {
346+
self[..].consensus_encode(s)
347+
}
348+
}
349+
350+
impl<T: Decodable + any::Any> Decodable for Vec<T> {
351+
#[inline]
352+
fn consensus_decode<D: crate::ReadExt>(mut d: D) -> Result<Self, Error> {
353+
if any::TypeId::of::<T>() == any::TypeId::of::<u8>() {
354+
let s = VarInt::consensus_decode(&mut d)?.0 as usize;
355+
if s > MAX_VEC_SIZE {
356+
return Err(self::Error::OversizedVectorAllocation {
357+
requested: s,
358+
max: MAX_VEC_SIZE,
359+
});
360+
}
361+
let mut v = vec![0; s];
362+
d.read_slice(&mut v)?;
363+
// SAFETY: checked that T is exactly u8, so v, of type, Vec<u8>, is exactly Vec<T>
364+
unsafe {
365+
Ok(std::mem::transmute::<Vec<u8>, Vec<T>>(v))
366+
}
367+
} else {
368+
let len = VarInt::consensus_decode(&mut d)?.0;
369+
let byte_size = (len as usize)
370+
.checked_mul(mem::size_of::<T>())
371+
.ok_or(self::Error::ParseFailed("Invalid length"))?;
372+
if byte_size > MAX_VEC_SIZE {
373+
return Err(self::Error::OversizedVectorAllocation {
374+
requested: byte_size,
375+
max: MAX_VEC_SIZE,
376+
});
349377
}
378+
let mut ret = Vec::with_capacity(len as usize);
379+
for _ in 0..len {
380+
ret.push(Decodable::consensus_decode(&mut d)?);
381+
}
382+
Ok(ret)
350383
}
351-
};
384+
}
385+
}
386+
387+
impl<T: Decodable + any::Any> Decodable for Box<[T]> {
388+
#[inline]
389+
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, Error> {
390+
let v = Vec::<T>::consensus_decode(d)?;
391+
Ok(v.into())
392+
}
352393
}
353-
impl_vec!(TxIn);
354-
impl_vec!(TxOut);
355-
impl_vec!(Transaction);
356-
impl_vec!(TapLeafHash);
357-
impl_vec!(Vec<u8>); // Vec<Vec<u8>>
358394

359395
macro_rules! impl_array {
360396
( $size:literal ) => {
@@ -383,38 +419,6 @@ impl_array!(4);
383419
impl_array!(32);
384420
impl_array!(33);
385421

386-
impl Encodable for Box<[u8]> {
387-
fn consensus_encode<W: io::Write>(&self, mut w: W) -> Result<usize, Error> {
388-
consensus_encode_with_size(&self[..], &mut w)
389-
}
390-
}
391-
impl Decodable for Box<[u8]> {
392-
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, Error> {
393-
let v = Vec::<u8>::consensus_decode(d)?;
394-
Ok(v.into())
395-
}
396-
}
397-
398-
impl Encodable for Vec<u8> {
399-
fn consensus_encode<W: io::Write>(&self, mut w: W) -> Result<usize, Error> {
400-
consensus_encode_with_size(&self[..], &mut w)
401-
}
402-
}
403-
impl Decodable for Vec<u8> {
404-
fn consensus_decode<D: crate::ReadExt>(mut d: D) -> Result<Self, Error> {
405-
let s = VarInt::consensus_decode(&mut d)?.0 as usize;
406-
if s > MAX_VEC_SIZE {
407-
return Err(self::Error::OversizedVectorAllocation {
408-
requested: s,
409-
max: MAX_VEC_SIZE,
410-
});
411-
}
412-
let mut v = vec![0; s];
413-
d.read_slice(&mut v)?;
414-
Ok(v)
415-
}
416-
}
417-
418422
macro_rules! impl_box_option {
419423
($type: ty) => {
420424
impl Encodable for Option<Box<$type>> {

0 commit comments

Comments
 (0)