Concrete & Generic Types
Generics
Often time, it can be useful to make a generic contract, such as:
#![allow(unused)] fn main() { struct GenericA { send_to: Box<dyn Compilable> } }
or
#![allow(unused)] fn main() { struct GenericB<T:Compilable> { send_to: T } }
In GenericA we use a trait object to allow us to let the send_to field
equal any Compilable type while having the same type GenericA,
whereas GenericB takes a type parameter that makes the GenericB more
specifically typed.
To highlight the differences between the approaches, suppose I had a parent contract:
#![allow(unused)] fn main() { #[derive(Serialize, Deserialize, JsonSchema)] struct ConcreteA; #[derive(Serialize, Deserialize, JsonSchema)] struct ConcreteB; struct AliceAndBobFree { alice: GenericA; bob: GenericA; } /// inner types can differ let example_free_ok = AliceAndBobFree { alice: GenericA{send_to: Box::new(ConcreteA)}, bob: GenericA{send_to:Box::new(ConcreteB)}}; struct AliceAndBobRestricted<T> { alice: GenericB<T>; bob: GenericB<T>; } /// inner types cannot differ let example_restricted_fails = AliceAndBobRestricted { alice: GenericB{send_to: ConcreteA}, bob: GenericB{send_to: ConcreteB}}; }
It might seem like you always want to use the GenericA variant, but there are cases where you
might want to guarantee that Alice and Bob's supplied contracts are the same type.
Concrete Wrappers
When you do have a generic type (either with trait objects or otherwise) it
can be difficult to use across an application boundary. To get around this,
one can create a wrapper type (or enum) that uses the TryFrom
paradigm to provide paths for the type to be concrete. E.g.,
#![allow(unused)] fn main() { #[derive(Serialize, Deserialize, JsonSchema)] enum Concrete { A(ConcreteA), B(ConcreteB), } impl TryFrom<Concrete> for GenericA { type Error = &'static str; fn try_from(concrete:Concrete) -> Result<Self, Self::Error> { match concrete { Concrete::A(a) => GenericA(Box::new(a)), Concrete::B(b) => GenericA(Box::new(b)) } } } }
Thus a Concrete can be used in a Serialize/Deserialize/JsonSchema API bound
context, whereas a GenericA could not.