@solana/options
This package allows us to manage and serialize Rust-like Option types in JavaScript. It can be used standalone, but it is also exported as part of the Solana JavaScript SDK @solana/web3.js@experimental
.
This package is also part of the @solana/codecs
package which acts as an entry point for all codec packages as well as for their documentation.
Creating options
In Rust, we define optional values as an Option<T>
type which can either be Some(T)
or None
. This is usually represented as T | null
in the JavaScript world. The issue with this approach is it doesn't work with nested options. For instance, an Option<Option<T>>
in Rust would become a T | null | null
in JavaScript which is equivalent to T | null
. That means, there is no way for us to represent the Some(None)
value in JavaScript or any other nested option.
To solve this issue, this library provides an Option<T>
union type that works very similarly to the Rust Option<T>
type. It is defined as follows:
type Option<T> = Some<T> | None;
type Some<T> = { __option: 'Some'; value: T };
type None = { __option: 'None' };
To improve the developer experience, helper functions are available to help you create options. The type T
of the option can either be inferred by TypeScript or explicitly provided.
some('Hello World');
some<number | string>(123);
none();
none<number | string>();
Option helpers
This library also provides helper functions to help us identify and manage Option
types.
For instance, you can use the isSome
and isNone
type guards to check whether a given Option
is of the desired type.
isSome(some('Hello World'));
isSome(none());
isNone(some('Hello World'));
isNone(none());
If you are given a type T | null
, you may also use the wrapNullable
helper function to transform it into an Option<T>
type.
wrapNullable('Hello world');
wrapNullable(null);
Unwrapping options
Several helpers are available to help you unwrap your options and access their potential value. For instance, the unwrapOption
function transforms an Option<T>
type into T
if the value exits and null
otherwise.
unwrapOption(some('Hello World'));
unwrapOption(none());
If null
isn’t the value you want to use for None
options, you may provide a custom fallback function as the second argument. Its return value will be assigned to None
options.
unwrapOption(some('Hello World'), () => 'Default');
unwrapOption(none(), () => 'Default');
Note that this unwrapOption
function does not recursively unwrap nested options. You may use the unwrapOptionRecursively
function for that purpose instead.
unwrapOptionRecursively(some(some(some('Hello World'))));
unwrapOptionRecursively(some(some(none<string>())));
The unwrapOptionRecursively
function also walks any object and array it encounters and recursively unwraps any option it identifies in its journey without mutating any object or array.
unwrapOptionRecursively({
a: 'hello',
b: none(),
c: [{ c1: some(42) }, { c2: none() }],
});
The unwrapOptionRecursively
also accepts a fallback function as a second argument to provide custom values for None
options.
unwrapOptionRecursively(
{
a: 'hello',
b: none(),
c: [{ c1: some(42) }, { c2: none() }],
},
() => 'Default',
);
Option codec
The getOptionCodec
function behaves exactly the same as the getNullableCodec
except that it encodes Option<T>
types instead of T | null
types.
Namely, it accepts a codec of type T
and returns a codec of type Option<T>
. It stores whether or not the item exists as a boolean prefix using a u8
by default.
const optionStringCodec = getOptionCodec(addCodecSizePrefix(getUtf8Codec(), getU32Codec()));
optionStringCodec.encode(some('Hi'));
optionStringCodec.encode(none());
You may provide a number codec as the prefix
option of the getOptionCodec
function to configure how to store the boolean prefix.
const u32OptionStringCodec = getOptionCodec(addCodecSizePrefix(getUtf8Codec(), getU32Codec()), {
prefix: getU32Codec(),
});
u32OptionStringCodec.encode(some('Hi'));
u32OptionStringCodec.encode(none());
Additionally, if the item is a FixedSizeCodec
, you may set the fixed
option to true
to also make the returned option codec a FixedSizeCodec
. To do so, it will pad null
values with zeroes to match the length of existing values.
const fixedOptionStringCodec = getOptionCodec(
fixCodecSize(getUtf8Codec(), 8),
{ fixed: true },
);
fixedOptionStringCodec.encode(some('Hi'));
fixedOptionStringCodec.encode(none());
Separate getOptionEncoder
and getOptionDecoder
functions are also available.
const bytes = getOptionEncoder(getU32Encoder()).encode(some(42));
const value = getOptionDecoder(getU32Decoder()).decode(bytes);
Zeroable option codec
Similarly to the getOptionCodec
function, The getZeroableOptionCodec
function accepts a codec of type T
and returns a codec of type Option<T>
. However, instead of relying on a boolean prefix to determine whether the item exists, it uses a zero value to represent None
. This means, you may only use this codec with fixed-size items.
const codec = getZeroableOptionCodec(getU16Codec());
codec.encode(some(42));
codec.encode(none());
codec.decode(new Uint8Array([42, 0]));
codec.encode(new Uint8Array([0, 0]));
As you can see, by default, it uses a Uint8Array
of zeroes to represent None
. However, you may provide a custom zero value that will be used to encode/decode None values. Note that this zero value must be a Uint8Array
of the same length as the codec's fixed size.
const codec = getZeroableOptionCodec(getU16Codec(), {
zeroValue: new Uint8Array([255, 255]),
});
codec.encode(some(42));
codec.encode(none());
codec.encode(new Uint8Array([0, 0]));
codec.decode(new Uint8Array([42, 0]));
codec.decode(new Uint8Array([255, 255]));
Separate getZeroableOptionEncoder
and getZeroableOptionDecoder
functions are also available.
const bytes = getZeroableOptionEncoder(getU16Encoder()).encode(some(42));
const value = getZeroableOptionDecoder(getU16Decoder()).decode(bytes);
To read more about the available codecs and how to use them, check out the documentation of the main @solana/codecs
package.