SCALE codec implementation in Go compatible with a Tinygo based toolchain
The SCALE types in Go are represented by a set of custom-defined types that implement the Encodable interface.
Each type also has a corresponding decode function using the convention Decode<TypeName>. Note that the type to which data should be decoded is inferred from the context and is not self-contained in the SCALE-encoded data.
One exception is the Tuple type. It doesn't have methods attached. Instead, there are EncodeTuple and DecodeTuple functions that can be invoked with any custom struct that embeds the Tuple interface.
Some quirks deserve mention. For example, the FixedSequence type, which has the same representation as the Sequence type, facilitates the encoding of arrays. As arrays are fixed-size sequences, they cannot be encoded as the Sequence type. Note that there are no type checks on the size.
The use of custom-defined types and generics reduces reliance on reflection, which isn't fully supported by TinyGo.
Go structs are encoded as SCALE Tuple, where each struct field is encoded in a sequence containing all the fields.
To decode SCALE encoded structs, it is required to have prior knowledge of the destination data type.
Panicking is not particularly helpful, especially in the Runtime. Instead, errors should be passed on to the Host. To accomplish that, all Decode functions should be refactored to return an error in addition to their current output, rather than simply panicking.
Array types have fixed size known at compile time, thus the size encoding is omitted.
From the spec: In some cases, the length indicator is omitted if the length of the sequence is fixed and known by the decoder upfront. Such cases are explicitly stated by the definition of the corresponding type.
Go proposal that might help to make generic parameterized encoding of Array (Sequence with fixed size): golang/go#44253
Since U8 is used extensively, e.g. Sequence[U8], invoking value.Bytes() to return the bytes causes a significant performance overhead with the custom gc, and it tries to allocate single allocation bigger than the max possible.
Currently, DecodeResult relies on the convention that for each type there is a Decode<TypeName> function. It invokes the decode function only for the predefined types, not for custom-defined ones.
res:=DecodeResult[MyOk, MyError](buffer)
We need to be able to dynamically build/call the corresponding decode function for the specified type using reflection.
Approach 1: Obtain the full package name of the corresponding function. We could potentially derive it from the Type. However, we'll rely on the convention that the decode function is defined in the same package.
Approach 2: Include a Decode method as part of the interface, so there will be type checks, and each type will have a Decode method that can be called using reflection.