Crate scale_info

source ·
Expand description

Efficient and space-efficient serialization of Rust types.

This library provides structures to easily retrieve compile-time type information at runtime and also to serialize this information in a space-efficient form, aka PortableForm.

§Registry

At the heart of its functionality is the Registry that acts as a cache for known types in order to efficiently deduplicate types and ensure a space-efficient serialization.

§Type Information

Information about types is provided via the TypeInfo trait.

This trait should be implemented for all types that are serializable. scale-info provides implementations for all commonly used Rust standard types and a derive macro for implementing of custom types.

§Deriving TypeInfo

Enable the derive feature of this crate:

scale-info = { version = "2.0.0", features = ["derive"] }
use scale_info::TypeInfo;

#[derive(TypeInfo)]
struct MyStruct {
    a: u32,
    b: MyEnum,
}

#[derive(TypeInfo)]
enum MyEnum {
    A(bool),
    B { f: Vec<u8> },
    C,
}

§Attributes

§#[scale_info(bounds(..))]

Replace the auto-generated where clause bounds for the derived TypeInfo implementation.

#[derive(TypeInfo)]
#[scale_info(bounds(T: TypeInfo + 'static))]
struct MyStruct<T> {
    a: Vec<T>,
}

The derive macro automatically adds TypeInfo bounds for all type parameters, and all field types containing type parameters or associated types.

This is naive and sometimes adds unnecessary bounds, since on a syntactical level there is no way to differentiate between a generic type constructor and a type alias with a generic argument e.g.

trait MyTrait {
    type A;
}

type MyAlias<T> = <T as MyTrait>::A;

#[derive(TypeInfo)]
struct MyStruct<T> {
    a: MyAlias<T>,
    b: Vec<T>,
}

So for the above, since a MyAlias<T>: TypeInfo bound is required, and we can’t distinguish between MyAlias<T> and Vec<T>, then the TypeInfo bound is simply added for all fields which contain any type param. In this case the redundant Vec<T>: TypeInfo would be added.

This is usually okay, but in some circumstances can cause problems, for example with the [overflow evaluating the requirement] error here.

The bounds attribute provides an “escape hatch” to allow the programmer control of the where clause on the generated impl, to solve this and other issues that can’t be foreseen by the auto-generated bounds heuristic.

§#[scale_info(skip_type_params(..))]

Remove the requirement for the specified type params to implement TypeInfo.

Consider a simple example of a type parameter which is used for associated types, but the type itself does not carry any type information. Consider this common pattern:

trait Config {
    type Balance;
}

struct Runtime; // doesn't implement `TypeInfo`

impl Config for Runtime {
    type Balance = u64;
}

#[allow(unused)]
#[derive(TypeInfo)]
#[scale_info(skip_type_params(T))]
struct A<T: Config> {
    balance: T::Balance,
    marker: core::marker::PhantomData<T>,
}

fn assert_type_info<T: scale_info::TypeInfo + 'static>() {}

fn main() {
    // without the `skip_type_params` attribute this will fail.
    assert_type_info::<A<Runtime>>();
}

By default, the derived TypeInfo implementation will add the type of all type parameters to the TypeParameter specification e.g.

type_params(vec![TypeParameter::new("T", Some(MetaType::new::<T>()))])

In the example above, this will cause a compiler error because Runtime is the concrete tyoe for T, which does not satisfy the TypeInfo requirement of MetaType::new::<T>().

Simply adding a TypeInfo derive to Runtime is one way of solving this, but that could be misleading (why does it need TypeInfo if a value of that type is never encoded?), and can sometimes require adding TypeInfo bounds in other impl blocks.

The skip_type_params attribute solves this, providing an additional “escape hatch” which prevents the given type parameter’s type information being required:

type_params(vec![TypeParameter::new("T", None)])

The generated type params do not now require T to implement TypeInfo, so the auto-generated bound is not added to the generated TypeInfo where clause.

§Combining bounds and skip_type_params

These two attributes can complement one another, particularly in the case where using bounds would still require manually adding a TypeInfo bound for the type parameter:

#[derive(TypeInfo)]
#[scale_info(bounds(), skip_type_params(T))]
struct A<T> {
    marker: core::marker::PhantomData<T>,
}

Without skip_type_params in the example above, it would require the TypeInfo bounds for T to be added manually e.g. #[scale_info(bounds(T: TypeInfo + 'static))]. Since the intention of the empty bounds is to remove all type bounds, then the addition of skip_type_params allows this to compile successfully.

§Precedence

When used independently, both attributes modify the where clause of the derived TypeInfo impl. When used together, the predicates supplied in the bounds attribute replaces all auto-generated bounds, and skip_type_params will have no effect on the resulting where clause.

Note: When using bounds without skip_type_params, it is therefore required to manually add a TypeInfo bound for any non skipped type parameters. The compiler will let you know.

§#[scale_info(capture_docs = "default|always|never")]

Docs for types, fields and variants can all be captured by the docs feature being enabled. This can be overridden using the capture_docs attribute:

#[scale_info(capture_docs = "default")] will capture docs iff the docs feature is enabled. This is the default if capture_docs is not specified.

#[scale_info(capture_docs = "always")] will capture docs for the annotated type even if the docs feature is not enabled.

#[scale_info(capture_docs = "never")] will not capture docs for the annotated type even if the docs is enabled.

This is useful e.g. when compiling metadata into a Wasm blob, and it is desirable to keep the binary size as small as possible, so the docs feature would be disabled. In case the docs for some types is necessary they could be enabled on a per-type basis with the above attribute.

§#[scale_info(crate = path::to::crate)]

Specify a path to the scale-info crate instance to use when referring to the APIs from generated code. This is normally only applicable when invoking re-exported scale-info derives from a public macro in a different crate. For example:

use scale_info_reexport::info::TypeInfo;

#[derive(TypeInfo)]
#[scale_info(crate = scale_info_reexport::info)]
enum TestEnum {
    FirstVariant,
    SecondVariant,
}
§#[scale_info(replace_segment("search", "replace"))]

Specify to rename a segment in the path returned by the [TypeInfo::path] function. Normally the path is generated by using the module_path! macro. This path includes the crate name and all the modules up to the declaration of the type. Sometimes it is useful to replace one of these segments to ensure that for example a renaming of the crate isn’t propagated to downstream users. Be aware that if a crate-name contains an hypen, the actual segment is crate_name. The replace name needs to be a valid Rust identifier. The attribute is allowed to be passed multiple times to replace multiple segments.

Example:

use scale_info_reexport::info::TypeInfo;

#[derive(TypeInfo)]
#[scale_info(replace_segment("something", "better_name"))]
#[scale_info(replace_segment("TestEnum", "BetterEnumName"))]
enum TestEnum {
    FirstVariant,
    SecondVariant,
}

§Forms

To bridge between compile-time type information and runtime the MetaForm is used to easily retrieve all information needed to uniquely identify types.

The MetaForm and its associated Registry can be transformed into the space-efficient form by the IntoPortable trait; it is used internally by the Registry in order to convert the expanded types into their space-efficient form.

§Symbols and Namespaces

To differentiate two types sharing the same name, namespaces are used. Commonly the namespace is equal to the one where the type has been defined in. For Rust prelude types such as Option and Result the root namespace (empty namespace) is used.

To use this library simply use the MetaForm initially with your own data structures; make them generic over the Form trait just as has been done in this crate with TypeInfo in order to get a simple implementation of IntoPortable. Use a single instance of the Registry for compaction and provide this registry instance upon serialization.

A usage example can be found in ink! here: https://github.com/paritytech/ink/blob/master/abi/src/specs.rs

Modules§

  • Builders for defining metadata for variant types (enums), and composite types (structs). They are designed to allow only construction of valid definitions.
  • Provides form definitions.
  • Interning data structure and associated symbol definitions.
  • Exports from std, core and alloc crates.

Macros§

Structs§

Enums§

  • An error that may be encountered upon constructing namespaces.
  • The possible types a SCALE encodable Rust value could have.
  • A primitive Rust type.

Traits§

  • Convert the type definition into the portable form using a registry.
  • Convenience trait for implementors, combining TypeInfo and 'static bounds.
  • Implementors return their meta type information.

Functions§

  • Returns the runtime bridge to the types compile-time type information.