Generics and Trait Constraints

Generics as Constraints

At a high level, Sway allows you to define constraints, or restrictions, that allow you to strike a balance between writing abstract and reusable code and enforcing compile-time checks to determine if the abstract code that you've written is correct.

The "abstract and reusable" part largely comes from generic types and the "enforcing compile-time checks" part largely comes from trait constraints. Generic types can be used with functions, structs, and enums (as we have seen in this book), but they can also be used with traits.

Generic Traits

Combining generic types with traits allows you to write abstract and reusable traits that can be implemented for any number of data types.

For example, imagine that you want to write a trait for converting between different types. This would be similar to Rust's Into and From traits. In Sway your conversion trait would look something like:

trait Convert<T> {
    fn from(t: T) -> Self;
}

The trait Convert takes a generic type T. Convert has one method from, which takes one parameter of type T and returns a Self. This means that when you implement Convert for a data type, from will return the type of that data type but will take as input the type that you define as T. Here is an example:

struct Square {
    width: u64,
}

struct Rectangle {
    width: u64,
    length: u64,
}

impl Convert<Square> for Rectangle {
    fn from(t: Square) -> Self {
        Self {
            width: t.width,
            length: t.width,
        }
    }
}

In this example, you have two different data types, Square and Rectangle. You know that all squares are rectangles and thus Square can convert into Rectangle (but not vice versa) and thus you can implement the conversion trait for those types.

If we want to call these methods we can do so by:

fn main() {
    let s = Square { width: 5 };
    let r = Rectangle::from(s);
}

Trait Constraints

Trait constraints allow you to use generic types and traits to place constraints on what abstract code you are willing to accept in your program as correct. These constraints take the form of compile-time checks for correctness.

If we wanted to use trait constraints with our Convert trait from the previous section we could do so like so:

fn into_rectangle<T>(t: T) -> Rectangle
where
    Rectangle: Convert<T>,
{
    Rectangle::from(t)
}

This function allows you to take any generic data type T and convert it to the type Rectangle as long as Convert<T> is implemented for Rectangle. Calling this function with a type T for which Convert<T> is not implemented for Rectangle will fail Sway's compile-time checks.