Types
This is an outline of all the types supported by borsh-construct
. Since Borsh is Rust-centric, some Rust snippets are included to make it clear what the equivalent Rust type is.
Numeric types
All numeric types mentioned in the Borsh spec are supported:
- Unsigned integers: U8, U16, U32, U64, U128
- Signed integers: I8, I16, I32, I64, I128
- Floats: F32, F64
- Bool (this is not explicitly part of the spec, but
borsh-rs
implements Bool as au8
with value 0 or 1)
Example:
>>> from borsh_construct import U32
>>> U32.build(42)
b'*\x00\x00\x00'
>>> U32.parse(b'*\x00\x00\x00')
42
Note
Most of the numeric types come directly from the construct
library, and are just aliased so that they match the Borsh spec. For example, borsh_construct.U8
is really construct.Int8ul
.
Fixed sized arrays
construct
gives us a nice []
syntax to represent fixed sized arrays. For example, an array of 3 u8 integers:
>>> from borsh_construct import U8
>>> U8[3].build([1, 2, 3])
b'\x01\x02\x03'
>>> U8[3].parse(b'\x01\x02\x03')
ListContainer([1, 2, 3])
This is what that fixed size array looks like in Rust:
let arr = [1u8, 2, 3];
Dynamic sized arrays
Dynamic arrays are implemented using the Vec
function:
>>> from borsh_construct import Vec, U8
>>> Vec(U8).build([1, 2, 3])
b'\x03\x00\x00\x00\x01\x02\x03'
>>> Vec(U8).parse(b'\x03\x00\x00\x00\x01\x02\x03')
ListContainer([1, 2, 3])
In Rust we could build that vector like this:
let v = vec![1u8, 2, 3];
C-like structs
This is analogous to a Rust struct with named fields:
>>> from borsh_construct import CStruct, String, U8
>>> person = CStruct(
... "name" / String,
... "age" / U8
... )
>>> person.build({"name": "Alice", "age": 50})
b'\x05\x00\x00\x00Alice2'
>>> person.parse(b'\x05\x00\x00\x00Alice2')
Container(name=u'Alice', age=50)
struct Person {
name: String,
age: u8,
}
Tuple structs
>>> from borsh_construct import TupleStruct, I32, F32
>>> pair = TupleStruct(I32, F32)
>>> pair.build([3, 0.5])
b'\x03\x00\x00\x00\x00\x00\x00?'
>>> pair.parse(b'\x03\x00\x00\x00\x00\x00\x00?')
ListContainer([3, 0.5])
struct Pair(i32, f32);
Enum
Rust's enum
is the trickiest part of borsh-construct
because it's rather different from Python's enum.Enum
. Under the hood, borsh-construct
uses the sumtypes
library to represent Rust enums in Python.
Notice below how our message
object has a .enum
attribute: this is the Python imitation of Rust's enum type.
Defining an enum:
>>> from borsh_construct import Enum, I32, CStruct, TupleStruct, String
>>> message = Enum(
... "Quit",
... "Move" / CStruct("x" / I32, "y" / I32),
... "Write" / TupleStruct(String),
... "ChangeColor" / TupleStruct(I32, I32, I32),
... enum_name="Message",
... )
>>> message.build(message.enum.Quit())
b'\x00'
>>> message.parse(b'\x00')
Message.Quit()
>>> message.build(message.enum.Move(x=1, y=3))
b'\x01\x01\x00\x00\x00\x03\x00\x00\x00'
>>> message.parse(b'\x01\x01\x00\x00\x00\x03\x00\x00\x00')
Message.Move(x=1, y=3)
>>> message.build(message.enum.Write(("hello",)))
b'\x02\x05\x00\x00\x00hello'
>>> message.parse(b'\x02\x05\x00\x00\x00hello')
Message.Write(tuple_data=ListContainer(['hello']))
>>> message.build(message.enum.ChangeColor((1, 2, 3)))
b'\x03\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
>>> message.parse(b'\x03\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')
Message.ChangeColor(tuple_data=ListContainer([1, 2, 3]))
Notice also how each variant of the enum is a subclass of the enum itself:
>>> assert isinstance(message.enum.Quit(), message.enum)
Rust type:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
HashMap
You can think of HashMap as a Python dictionary as long as the keys and values have a well-defined type.
>>> from borsh_construct import HashMap, String, U32
>>> scores = HashMap(String, U32)
>>> scores.build({"Blue": 10, "Yellow": 50})
b'\x02\x00\x00\x00\x04\x00\x00\x00Blue\n\x00\x00\x00\x06\x00\x00\x00Yellow2\x00\x00\x00'
>>> scores.parse(b'\x02\x00\x00\x00\x04\x00\x00\x00Blue\n\x00\x00\x00\x06\x00\x00\x00Yellow2\x00\x00\x00')
{'Blue': 10, 'Yellow': 50}
fn main() {
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
}
HashSet
The HashSet is similar to a Python set
with a well-defined type.
>>> from borsh_construct import HashSet, I32
>>> a = HashSet(I32)
>>> a.build({1, 2, 3})
b'\x03\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
>>> a.parse(b'\x03\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00')
{1, 2, 3}
use std::collections::HashSet;
fn main() {
let mut a: HashSet<i32> = vec![1i32, 2, 3].into_iter().collect();
}
Option
Rust programmers will notice that our Option type is not implemented like a Rust enum, because it's not worth the complexity.
>>> from borsh_construct import Option, U8
>>> optional_num = Option(U8)
>>> optional_num.build(None)
b'\x00'
>>> optional_num.parse(b'\x00') is None
True
>>> optional_num.build(3)
b'\x01\x03'
>>> optional_num.parse(b'\x01\x03')
3
Option<u8>
Bytes
The Borsh spec doesn't specifically mention serializing raw bytes, but it's worth including anyway:
>>> from borsh_construct import Bytes
>>> Bytes.build(bytes([1, 2, 3]))
b'\x03\x00\x00\x00\x01\x02\x03'
>>> Bytes.parse(b'\x03\x00\x00\x00\x01\x02\x03')
b'\x01\x02\x03'
vec![1u8, 2, 3]
String
Python:
>>> from borsh_construct import String
>>> String.build("🚀🚀🚀")
b'\x0c\x00\x00\x00\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80'
>>> String.parse(b'\x0c\x00\x00\x00\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80\xf0\x9f\x9a\x80')
'🚀🚀🚀'
Rust type:
String::from("🚀🚀🚀")