Repository: near/borsh-rs
Pull request #99 (2022-08-03 11:52:56)

feat: add proper support for isize and usize
Fixes https://github.com/near/borsh-rs/issues/40 and https://github.com/near/borsh-rs/issues/49

Technically, `isize`/`usize` have different deserialization semantics from `i64`/`u64`, but since `isize`/`usize` are not a part of the spec I guess we don't really have many options.

matklad: Makes sense to me: in general, it's not true that borsh types correspond to rust types (eg, collections are restricted to u32::MAX elements only), so this seems exactly like how it should be.
itegulov: Also, just noticed that we have `BorshSchema` impl for `char`, but not `BorshSerialize` or `BorshDeserialize`. Furthermore, the spec does not mention char in any way, so it seems like the schema impl should be deleted?

Patch:
diff --git a/borsh/src/de/mod.rs b/borsh/src/de/mod.rs
index eedf6c87..208d59a1 100644
--- a/borsh/src/de/mod.rs
+++ b/borsh/src/de/mod.rs
@@ -23,6 +23,7 @@ mod hint;
 
 const ERROR_NOT_ALL_BYTES_READ: &str = "Not all bytes read";
 const ERROR_UNEXPECTED_LENGTH_OF_INPUT: &str = "Unexpected length of input";
+const ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_ISIZE: &str = "Overflow on machine with 32 bit isize";
 const ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_USIZE: &str = "Overflow on machine with 32 bit usize";
 const ERROR_INVALID_ZERO_VALUE: &str = "Expected a non-zero value";
 
@@ -157,6 +158,19 @@ impl_for_nonzero_integer!(core::num::NonZeroU64);
 impl_for_nonzero_integer!(core::num::NonZeroU128);
 impl_for_nonzero_integer!(core::num::NonZeroUsize);
 
+impl BorshDeserialize for isize {
+    fn deserialize(buf: &mut &[u8]) -> Result<Self> {
+        let i: i64 = BorshDeserialize::deserialize(buf)?;
+        let i = isize::try_from(i).map_err(|_| {
+            Error::new(
+                ErrorKind::InvalidInput,
+                ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_ISIZE,
+            )
+        })?;
+        Ok(i)
+    }
+}
+
 impl BorshDeserialize for usize {
     fn deserialize(buf: &mut &[u8]) -> Result<Self> {
         let u: u64 = BorshDeserialize::deserialize(buf)?;
diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs
index 80921d0a..f0303d0e 100644
--- a/borsh/src/schema.rs
+++ b/borsh/src/schema.rs
@@ -145,6 +145,8 @@ macro_rules! impl_for_primitives {
 impl_for_primitives!(bool char f32 f64 i8 i16 i32 i64 i128 u8 u16 u32 u64 u128);
 impl_for_renamed_primitives!(String: string);
 impl_for_renamed_primitives!(str: string);
+impl_for_renamed_primitives!(isize: i64);
+impl_for_renamed_primitives!(usize: u64);
 
 #[cfg(not(feature = "const-generics"))]
 const _: () = {
diff --git a/borsh/src/ser/mod.rs b/borsh/src/ser/mod.rs
index 212d0463..f284a1f7 100644
--- a/borsh/src/ser/mod.rs
+++ b/borsh/src/ser/mod.rs
@@ -118,6 +118,12 @@ impl_for_nonzero_integer!(core::num::NonZeroU64);
 impl_for_nonzero_integer!(core::num::NonZeroU128);
 impl_for_nonzero_integer!(core::num::NonZeroUsize);
 
+impl BorshSerialize for isize {
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
+        BorshSerialize::serialize(&(*self as i64), writer)
+    }
+}
+
 impl BorshSerialize for usize {
     fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
         BorshSerialize::serialize(&(*self as u64), writer)


Test patch:
diff --git a/borsh/tests/test_primitives.rs b/borsh/tests/test_primitives.rs
new file mode 100644
index 00000000..df3dd274
--- /dev/null
+++ b/borsh/tests/test_primitives.rs
@@ -0,0 +1,22 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+macro_rules! test_primitive {
+    ($test_name: ident, $v: expr, $t: ty) => {
+        #[test]
+        fn $test_name() {
+            let expected: $t = $v;
+            let buf = expected.try_to_vec().unwrap();
+            let actual = <$t>::try_from_slice(&buf).expect("failed to deserialize");
+            assert_eq!(actual, expected);
+        }
+    };
+}
+
+test_primitive!(test_isize_neg, -100isize, isize);
+test_primitive!(test_isize_pos, 100isize, isize);
+test_primitive!(test_isize_min, isize::min_value(), isize);
+test_primitive!(test_isize_max, isize::max_value(), isize);
+
+test_primitive!(test_usize, 100usize, usize);
+test_primitive!(test_usize_min, usize::min_value(), usize);
+test_primitive!(test_usize_max, usize::max_value(), usize);
diff --git a/borsh/tests/test_schema_primitives.rs b/borsh/tests/test_schema_primitives.rs
new file mode 100644
index 00000000..bacaf1e6
--- /dev/null
+++ b/borsh/tests/test_schema_primitives.rs
@@ -0,0 +1,25 @@
+use borsh::schema::*;
+
+#[test]
+fn isize_schema() {
+    let schema = isize::schema_container();
+    assert_eq!(
+        schema,
+        BorshSchemaContainer {
+            declaration: "i64".to_string(),
+            definitions: Default::default()
+        }
+    )
+}
+
+#[test]
+fn usize_schema() {
+    let schema = usize::schema_container();
+    assert_eq!(
+        schema,
+        BorshSchemaContainer {
+            declaration: "u64".to_string(),
+            definitions: Default::default()
+        }
+    )
+}