yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Yong HeFix use of variadic generics with [Differentiable]. (#8736)bedc3421c

master
37.0 KiB1111 linesraw

layout: user-guide permalink: /user-guide/interfaces-generics

Interfaces and Generics

This chapter covers two interrelated Slang language features: interfaces and generics. We will talk about what they are, how they relate to similar features in other languages, how they are parsed and translated by the compiler, and show examples on how these features simplify and modularize shader code.

Interfaces

Interfaces are used to define the methods and services a type should provide. You can define a interface as the following example:

interface IFoo
{
    int myMethod(float arg);
}

Slang's syntax for defining interfaces are similar to interfaces in C# and protocols in Swift. In this example, the IFoo interface establishes a contract that any type conforming to this interface must provide a method named myMethod that accepts a float argument and returns an int value.

A struct type may declare its conformance to an interface via the following syntax:

struct MyType : IFoo
{
    int myMethod(float arg)
    {
        return (int)arg + 1;
    }
}

By declaring the conformance to IFoo, the definition of MyType must include a method named myMethod with a matching signature to that defined in the IFoo interface to satisfy the declared conformance. If a type misses any methods required by the interface, the Slang compiler will generate an error message.

A struct type may declare multiple interface conformances:

interface IBar { uint myMethod2(uint2 x); }

struct MyType : IFoo, IBar
{
    int myMethod(float arg) {...}
    uint myMethod2(uint2 x) {...}
}

In this case, the definition of MyType must satisfy the requirements from both the IFoo and IBar interfaces by providing both the myMethod and myMethod2 methods.

Interface methods can have a default implementation, which will be used if a conforming type doesn't provide an overriding implementation. For example:

interface IFoo
{
    int getVal() { return 0; }
}

// OK, MyType.getVal() will use the default implementation provided in `IFoo`.
struct MyType : IFoo {}

A concrete type that provides its overriding implementation to an interface method requirement that has a default implementation must be explicitly marked as 'override'. For example:

struct MyType2 : IFoo
{
    // Explicitly mark `getVal` as `override` is needed
    // because `IFoo.getVal` has a body.
    override int getVal() { return 1; }
}

Generics

Generics can be used to eliminate duplicate code for shared logic that operates on different types. The following example shows how to define a generic method in Slang.

int myGenericMethod<T>(T arg) where T : IFoo
{
    return arg.myMethod(1.0);
}

The above listing defines a generic method named myGenericMethod, which accepts an argument that can be of any type T as long as T conforms to the IFoo interface. The T here is called a generic type parameter, and it is associated with an type constraint in the following where clause to indicate that any type represented by T must conform to the interface IFoo.

The following listing shows how to invoke a generic method:

MyType obj;
int a = myGenericMethod<MyType>(obj); // OK, explicit type argument
int b = myGenericMethod(obj); // OK, automatic type deduction

You may explicitly specify the concrete type to used for the generic type argument, by providing the types in angular brackets after the method name, or leave it to the compiler to automatically deduce the type from the argument list.

Note that it is important to associate a generic type parameter with a type constraint. In the above example, although the definition of myGenericMethod is agnostic of the concrete type T will stand for, knowing that T conforms to IFoo allows the compiler to type-check and pre-compile myGenericMethod without needing to substitute T with any concrete types first. Similar to languages like C#, Rust, Swift and Java, leaving out the type constraint declaration on type parameter T will result in a compile error at the line calling arg.myMethod since the compiler cannot verify that arg has a member named myMethod without any knowledge on T. This is a major difference of Slang's generics compared to templates in C++.

While C++ templates are a powerful language mechanism, Slang has followed the path of many other modern programming languages to adopt the more structural and restricted generics feature instead. This enables the Slang compiler to perform type checking early to give more readable error messages, and to speed-up compilation by reusing a lot of work for different instantiations of myGenericMethod.

A generic parameter can also be a value. Currently, integer, bool and enum types are allowed as the type for a generic value parameter. Generic value parameters are declared with the let keyword. For example:

void g1<let n : int>() { ... }

enum MyEnum { A, B, C }
void g2<let e : MyEnum>() { ... }

void g3<let b : bool>() { ... }

Alternative Syntax

Alternatively, you can use __generic keyword to define generic parameters before the method:

__generic<typename T> // `typename` is optional.
int myGenericMethod(T arg) where T : IFoo
{
    return arg.myMethod(1.0);
}

The same method can be defined in an alternative simplified syntax without the where clause:

int myGenericMethod<T:IFoo>(T arg) { ... }

Generic value parameters can also be defined using the traditional C-style syntax:

void g1<typename T, int n>() { ... }

Slang allows multiple where clauses, and multiple interface types in a single where clause:

struct MyType<T, U>
    where T: IFoo, IBar
    where U : IBaz<T>
{
}
// equivalent to:
struct MyType<T, U>
    where T: IFoo
    where T : IBar
    where U : IBaz<T>
{
}

Optional conformances can be expressed compactly using the where optional syntax:

// Together, these two overloads...
int myGenericMethod<T>(T arg)
{
}

int myGenericMethod<T>(T arg) where T: IFoo
{
    arg.myMethod(1.0);
}

// ... are equivalent to:
int myGenericMethod<T>(T arg) where optional T: IFoo
{
    if (T is IFoo)
    {
        arg.myMethod(1.0); // OK in a block that checks for T: IFoo conformance.
    }
}

Supported Constructs in Interface Definitions

Slang supports many other constructs in addition to ordinary methods as a part of an interface definition.

Properties

interface IFoo
{
    property int count {get; set;}
}

The above listing declares that any conforming type must define a property named count with both a getter and a setter method.

Generic Methods

interface IFoo
{
    int compute<T>(T val) where T : IBar;
}

The above listing declares that any conforming type must define a generic method named compute that has one generic type parameter conforming to the IBar interface.

Static Methods

interface IFoo
{
    static int compute(int val);
};

The above listing declares that any conforming type must define a static method named compute. This allows the following generic method to pass type-checking:

void f<T>() where T : IFoo
{
    T.compute(5); // OK, T has a static method `compute`.
}

Static Constants

You can define static constant requirements in an interface. The constants can be accessed in places where a compile-time constant is needed.

interface IMyValue
{
    static const int value;
}
struct MyObject2 : IMyValue
{
    static const int value = 2;
}
struct GetValuePlus1<T:IMyValue>
{
    static const int value = T.value + 1;
}

static const int result = GetValuePlus1<MyObject2>.value;  // result == 3

This Type

You may use a special keyword This in interface definitions to refer to the type that is conforming to the interface. The following examples demonstrate a use of This type:

interface IComparable
{
    int comparesTo(This other);
}
struct MyObject : IComparable
{
    int val;
    int comparesTo(MyObject other)
    {
        return val < other.val ? -1 : 1;
    }
}

In this example, the IComparable interface declares that any conforming type must provide a comparesTo method that performs a comparison between an object to another object of the same type. The MyObject type satisfies this requirement by providing a comparesTo method that accepts a MyObject typed argument, since in the scope of MyObject, This type is equivalent to MyObject.

Initializers

Consider a generic method that wants to create and initialize a new instance of generic type T:

void f<T:IFoo>()
{
    T obj = /*a newly initialized T*/
}

One way to implement this is to introduce a static method requirement in IFoo:

interface IFoo
{
    static This create();
}

With this interface definition, we can define f as following:

void f<T:IFoo>()
{
    T obj = T.create();
}

This solution works just fine, but it would be nicer if you can just write:

T obj = T();

Or simply

T obj;

And let the compiler invoke the default initializer defined in the type. To enable this, you can include an initializer requirement in the interface definition:

interface IFoo
{
    __init();
}

Initializers with parameters are supported as well. For example:

interface IFoo
{
    __init(int a, int b);
}
void g<T:IFoo>()
{
    T obj = {1, 2}; // OK, invoking the initializer on T.
}

Associated Types

When writing code using interfaces and generics, there are some situations where an interface method needs to return an object whose type is implementation-dependent. For example, consider the following IFloatContainer interface that represents a container of float values:

// Represents a container of float values.
interface IFloatContainer
{
    // Returns the number of elements in this container.
    uint getCount();
    // Returns an iterator representing the start of the container.
    Iterator begin();
    // Returns an iterator representing the end of the container.
    Iterator end();
    // Return the element at the location represented by `iter`.
    float getElementAt(Iterator iter);
}

An implementation of the IFloatContainer interface may use different types of iterators. For example, an implementation that is simply an array of floats can expose Iterator as a simple integer index:

struct ArrayFloatContainer : IFloatContainer
{
    float content[10];
    uint getCount() { return 10; }
    uint begin() { return 0; }
    uint end() { return 10; }
    float getElementAt(uint iter) { return content[iter]; }
}

On the other hand, an implementation that uses multiple buffers as the backing storage may use a more complex type to locate an element:

// Exposes values in two `StructuredBuffer`s as a single container.
struct MultiArrayFloatContainer : IFloatContainer
{
    StructuredBuffer<float> firstBuffer;
    StructuredBuffer<float> secondBuffer;
    uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); }

    // `uint2.x` indicates which buffer, `uint2.y` indicates the index within the buffer.
    uint2 begin() { return uint2(0,0); }
    uint2 end() { return uint2 (1, getBufferSize(secondBuffer)); }
    float getElementAt(uint2 iter)
    {
        if (iter.x == 0) return firstBuffer[iter.y];
        else return secondBuffer[iter.y];
    }
}

Ideally, a generic function that wishes to enumerate values in a IFloatContainer shouldn't need to care about the implementation details on what the concrete type of Iterator is, and we would like to be able to write the following:

float sum<T:IFloatContainer>(T container)
{
    float result = 0.0f;
    for (T.Iterator iter = container.begin(); iter != container.end(); iter=iter.next())
    {
        float val = container.getElementAt(iter);
        result += val;
    }
    return result;
}

Here the sum function simply wants to access all the elements and sum them up. The details of what the Iterator type actually is does not matter to the definition of sum.

The problem is that the IFloatContainer interface definition requires methods like begin(), end() and getElementAt() to refer to a iterator type that is implementation dependent. How should the signature of these methods be defined in the interface? The answer is to use associated types.

In addition to constructs listed in the previous section, Slang also supports defining associated types in an interface definition. An associated type can be defined as following.

// The interface for an iterator type.
interface IIterator
{
    // An iterator needs to know how to move to the next element.
    This next();
}

interface IFloatContainer
{
    // Requires an implementation to define a typed named `Iterator` that
    // conforms to the `IIterator` interface.
    associatedtype Iterator : IIterator;

    // Returns the number of elements in this container.
    uint getCount();
    // Returns an iterator representing the start of the container.
    Iterator begin();
    // Returns an iterator representing the end of the container.
    Iterator end();
    // Return the element at the location represented by `iter`.
    float getElementAt(Iterator iter);
};

This associatedtype definition in IFloatContainer requires that all types conforming to this interface must also define a type in its scope named Iterator, and this iterator type must conform to the IIterator interface. An implementation to the IFloatContainer interface by using either a typedef declaration or a struct definition inside its scope to satisfy the associated type requirement. For example, the ArrayFloatContainer can be implemented as following:

struct ArrayIterator : IIterator
{
    uint index;
    __init(int x) { index = x; }
    ArrayIterator next()
    {
        return ArrayIterator(index + 1);
    }
}
struct ArrayFloatContainer : IFloatContainer
{
    float content[10];

    // Specify that the associated `Iterator` type is `ArrayIterator`.
    typedef ArrayIterator Iterator;

    Iterator getCount() { return 10; }
    Iterator begin() { return ArrayIterator(0); }
    Iterator end() { return ArrayIterator(10); }
    float getElementAt(Iterator iter) { return content[iter.index]; }
}

Alternatively, you may also define the Iterator type directly inside a struct implementation, as in the following definition for MultiArrayFloatContainer:

// Exposes values in two `StructuredBuffer`s as a single container.
struct MultiArrayFloatContainer : IFloatContainer
{
    // Represents an iterator of this container
    struct Iterator : IIterator
    {
        // `index.x` indicates which buffer the element is located in.
        // `index.y` indicates which the index of the element inside the buffer.
        uint2 index;

        // We also need to keep a size of the first buffer so we know when to
        // switch to the second buffer.
        uint firstBufferSize;

        // Implementation of IIterator.next()
        Iterator next()
        {
            Iterator result;
            result.index.x = index.x;
            result.index.y = index.y + 1;
            // If we are at the end of the first buffer,
            // move to the head of the second buffer
            if (result.index.x == 0 && result.index.y == firstBufferSize)
            {
                result.index = uint2(1, 0);
            }
            return result;
        }
    }

    StructuredBuffer<float> firstBuffer;
    StructuredBuffer<float> secondBuffer;
    uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); }

    Iterator begin()
    {
        Iterator iter;
        iter.index = uint2(0, 0);
        iter.firstBufferSize = getBufferSize(firstBuffer);
        return iter;
    }
    Iterator end()
    {
        Iterator iter;
        iter.index = uint2(1, getBufferSize(secondBuffer));
        iter.firstBufferSize = 0;
        return iter;
    }
    float getElementAt(Iterator iter)
    {
        if (iter.index.x == 0) return firstBuffer[iter.index.y];
        else return secondBuffer[iter.index.y];
    }
}

In summary, an associatedtype requirement in an interface is similar to other types of requirements: a method requirement means that an implementation must provide a method matching the interface signature, while an associatedtype requirement means that an implementation must provide a type in its scope with the matching name and interface constraint. In general, when defining an interface that is producing and consuming an object whose actual type is implementation-dependent, the type of this object can often be modeled as an associated type in the interface.

Comparing Generics to C++ Templates

Readers who are familiar with C++ could easily relate the Iterator example in previous subsection to the implementation of STL. In C++, the sum function can be easily written with templates:

template<typename TContainer>
float sum(const TContainer& container)
{
    float result = 0.0f;
    // Assumes `TContainer` has a type `Iterator` that supports `operator++`.
    for (TContainer::Iterator iter = container.begin(); iter != container.end(); ++iter)
    {
        result += container.getElementAt(iter);
    }
    return result;
}

A C++ programmer can implement ArrayFloatContainer as following:

struct ArrayFloatContainer
{
    float content[10];

    typedef uint32_t Iterator;

    Iterator getCount() { return 10; }
    Iterator begin() { return 0; }
    Iterator end() { return 10; }
    float getElementAt(Iterator iter) { return content[iter]; }
};

Because C++ does not require a template function to define constraints on the templated type, there are no interfaces or inheritances involved in the definition of ArrayFloatContainer. However ArrayFloatContainer still needs to define what its Iterator type is, so the sum function can be successfully specialized with an ArrayFloatContainer.

Note that the biggest difference between C++ templates and generics is that templates are not type-checked prior to specialization, and therefore the code that consumes a templated type (TContainer in this example) can simply assume container has a method named getElementAt, and the TContainer scope provides a type definition for TContainer::Iterator. Compiler error only arises when the programmer is attempting to specialize the sum function with a type that does not meet these assumptions. Contrarily, Slang requires all possible uses of a generic type be declared through an interface. By stating that TContainer:IContainer in the generics declaration, the Slang compiler can verify that container.getElementAt is calling a valid function. Similarly, the interface also tells the compiler that TContainer.Iterator is a valid type and enables the compiler to fully type check the sum function without specializing it first.

Similarity to Swift and Rust

Slang's associatedtype shares the same semantic meaning with associatedtype in a Swift protocol or type in a Rust trait, except that Slang currently does not support the more general where clause in these languages. C# does not have an equivalent to associatedtype, and programmers need to resort to generic interfaces to achieve similar goals.

Generic Value Parameters

So far we have demonstrated generics with type parameters. Additionally, Slang also supports generic value parameters. The following listing shows an example of generic value parameters.

struct Array<T, let N : int>
{
    T arrayContent[N];
}

In this example, the Array type has a generic type parameter, T, that is used as the element type of the arrayContent array, and a generic value parameter N of integer type.

Note that the builtin vector<float, N> type also has an generic value parameter N.

Note

The only type of generic value parameters are int, uint and bool. float and other types cannot be used in a generic value parameter. Computations in a type expression are supported as long as they can be evaluated at compile time. For example, vector<float, 1+1> is allowed and considered equivalent to vector<float, 2>.

Type Equality Constraints

In addition to type conformance constraints as in where T : IFoo, Slang also supports type equality constraints. This is mostly useful in specifying additional constraints for associated types. For example:

interface IFoo { associatedtype A; }

// Access all T that conforms to IFoo, and T.A is `int`.
void foo<T>(T v)
    where T : IFoo
    where T.A == int
{
}

struct X : IFoo
{
    typealias A = int;
}

struct Y : IFoo
{
    typealias A = float;
}

void test()
{
    foo<X>(X()); // OK
    foo<Y>(Y()); // Error, `Y` cannot be used for `T`.
}

Interface-typed Values

So far we have been using interfaces as constraints to generic type parameters. For example, the following listing defines a generic function with a type parameter TTransform constrained by interface ITransform:

interface ITransform
{
    int compute(MyObject obj);
}

// Defining a generic method:
int apply<TTransform : ITransform>(TTransform transform, MyObject object)
{
    return transform.compute(object);
}

While Slang's syntax for defining generic methods bears similarity to generics in C#/Java and templates in C++ and should be easy to users who are familiar with these languages, codebases that make heavy use of generics can quickly become verbose and difficult to read. To reduce the amount of boilerplate, Slang supports an alternate way to define the apply method by using the interface type ITransform as parameter type directly:

// A method that is equivalent to `apply` but uses simpler syntax:
int apply_simple(ITransform transform, MyObject object)
{
    return transform.compute(object);
}

Instead of defining a generic type parameter TTransform and a method parameter transform that has TTransform type, you can simply define the same apply function like a normal method, with a transform parameter whose type is an interface. From the Slang compiler's view, apply and apply_simple will be compiled to the same target code.

In addition to parameters, Slang allows variables, and function return values to have an interface type as well:

ITransform test(ITransform arg)
{
    ITransform v = arg;
    return v;
}

Restrictions and Caveats

The Slang compiler always attempts to determine the actual type of an interface-typed value at compile time and specialize the code with the actual type. As long as the compiler can successfully determine the actual type, code that uses interface-typed values are equivalent to code written in the generics syntax. However, when interface types are used in function return values, the compiler will not be able to trivially propagate type information. For example:

ITransform getTransform(int x)
{
    if (x == 0)
    {
        Type1Transform rs = {};
        return rs;
    }
    else
    {
        Type2Transform rs = {};
        return rs;
    }
}

In this example, the actual type of the return value is dependent on the value of x, which may not be known at compile time. This means that the concrete type of the return value at invocation sites of getTransform may not be statically determinable. When the Slang compiler cannot infer the concrete type of an interface-type value, it will generate code that performs a dynamic dispatch based on the concrete type of the value at runtime, which may introduce performance overhead. Note that this behavior applies to function return values in the form of out parameters as well:

void getTransform(int x, out ITransform transform)
{
    if (x == 0)
    {
        Type1Transform rs = {};
        transform = rs;
    }
    else
    {
        Type2Transform rs = {};
        transform = rs;
    }
}

This getTransform definition can also result in dynamic dispatch code since the type of transform may not be statically determinable.

When the compiler is generating dynamic dispatch code for interface-typed values, it requires the concrete type of the interface-typed value to be free of any opaque-typed fields (e.g. resources and buffer types). A compiler error will generated upon such attempts:

struct MyTransform : ITransform
{
    StructuredBuffer<int> buffer;
    int compute(MyObject obj)
    {
        return buffer[0];
    }
}

ITransform getTransform(int x)
{
    MyTransform rs;
    // Error: cannot use an opaque value as an interface-typed return value.
    return rs;
}

Assigning different values to a mutable interface-typed variable also undermines the compiler's ability to statically determine the type of the variable, and is not supported by the Slang compiler today:

void test(int x)
{
    ITransform t = Type1Transform();
    // Do something ...
    // Assign a different type of transform to `t`:
    // (Not supported by Slang today)
    t = Type2Transform();
    // Do something else...
}

In general, if the use of interface-typed values is restricted to function parameters only, then the all code that involves interface-typed values will be compiled the same way as if the code is written using standard generics syntax.

Extending a Type with Additional Interface Conformances

In the previous chapter, we introduced the extension feature that lets you define new members to an existing type in a separate location outside the original definition of the type.

extensions can be used to make an existing type conform to additional interfaces. Suppose we have an interface IFoo and a type MyObject that implements the interface:

interface IFoo
{
    int foo();
};

struct MyObject : IFoo
{
    int foo() { return 0; }
}

Now we introduce another interface, IBar:

interface IBar
{
    float bar();
}

We can define an extension to make MyObject conform to IBar as well:

extension MyObject : IBar
{
    float bar() { return 1.0f }
}

With this extension, we can use MyObject in places that expects an IBar as well:

void use(IBar b)
{
    b.bar();
}

void test()
{
    MyObject obj;
    use(obj); // OK, `MyObject` is extended to conform to `IBar`.
}

You may define more than one interface conformances in a single extension:

interface IBar2
{
    float bar2();
}
extension MyObject : IBar, IBar2
{
    float bar() { return 1.0f }
    float bar2() { return 2.0f }
}

is and as Operator

You can use is operator to test if an interface-typed value is of a specific concrete type, and use as operator to downcast the value into a specific type. The as operator returns an Optional<T> that is not none if the downcast succeeds.

interface IFoo
{
    int foo();
}
struct MyImpl : IFoo
{
    int foo() { return 0; }
}
void test(IFoo foo)
{
    bool t = foo is MyImpl; // true
    Optional<MyImpl> optV = foo as MyImpl;
    if (t == (optV != none))
        printf("success");
    else
        printf("fail");
}
void main()
{
    MyImpl v;
    test(v);
}
// Result:
// "success"

In addition to casting from an interface type to a concrete type, as and is operator can be used on generic types as well to cast a generic type into a concrete type. For example:

T compute<T>(T a1, T a2)
{
    if (a1 is float)
    {
        return reinterpret<T>((a1 as float).value + (a2 as float).value);
    }
    else if (T is int)
    {
        return reinterpret<T>((a1 as int).value - (a2 as int).value);
    }
    return T();
}
// compute(1.0f, 2.0f) == 3.0f
// compute(3, 1) == 2

Since as operator returns a Optional<T> type, it can also be used in the if predicate to test if an object can be casted to a specific type, once the cast test is successful, the object can be used in the if block as the casted type without the need to retrieve the Optional<T>::value property, for example:

interface IFoo
{
    void foo();
}

struct MyImpl1 : IFoo
{
    void foo() { printf("MyImpl1");}
}

struct MyImpl2 : IFoo
{
    void foo() { printf("MyImpl2");}
}

struct MyImpl3 : IFoo
{
    void foo() { printf("MyImpl3");}
}

void test(IFoo foo)
{
    // This syntax will be desugared to the following:
    // {
    //      Optional<MyImpl1> optVar = foo as MyImpl1;
    //      if (optVar.hasValue)
    //      {
    //          MyImpl1 t = optVar.value;
    //          t.foo();
    //      }
    //      else if ...
    // }
    if (let t = foo as MyImpl1) // t is of type MyImpl1
    {
        t.foo();
    }
    else if (let t = foo as MyImpl2) // t is of type MyImpl2
    {
        t.foo();
    }
    else
        printf("fail");
}

void main()
{
    MyImpl1 v1;
    test(v1);

    MyImpl2 v2;
    test(v2);
}

See if-let syntax for more details.

Generic Interfaces

Slang allows interfaces themselves to be generic. A common use of generic interfaces is to define the IEnumerable type:

interface IEnumerator<T>
{
    This moveNext();
    bool isEnd();
    T getValue();
}

interface IEnumerable<T>
{
    associatedtype Enumerator : IEnumerator<T>;
    Enumerator getEnumerator();
}

You can constrain a generic type parameter to conform to a generic interface:

void traverse<TElement, TCollection>(TCollection c)
    where TCollection : IEnumerable<TElement>
{
    ...
}

Generic Extensions

You can use generic extensions to extend a generic type. For example,

interface IFoo { void foo(); }
interface IBar { void bar(); }

struct MyType<T : IFoo>
{
    void foo() { ... }
}

// Extend `MyType<T>` so it conforms to `IBar`.
extension<T:IFoo> MyType<T> : IBar
{
    void bar() { ... }
}
// Equivalent to:
__generic<T:IFoo>
extension MyType<T> : IBar
{
    void bar() { ... }
}

Extensions to Interfaces

In addition to extending ordinary types, you can define extensions on all types that conforms to some interface:

// An example interface.
interface IFoo
{
    int foo();
}

// Extend any type `T` that conforms to `IFoo` with a `bar` method.
extension<T:IFoo> T
{
    int bar() { return 0; }
}

int use(IFoo foo)
{
    // With the extension, all uses of `IFoo` typed values
    // can assume there is a `bar` method.
    return foo.bar();
}

Note that interface types cannot be extended, because extending an interface with new requirements would make all existing types that conforms to the interface no longer valid.

In the presence of extensions, it is possible for a type to have multiple ways to conform to an interface. In this case, Slang will always prefer the more specific conformance over the generic one. For example, the following code illustrates this behavior:

interface IBase{}
interface IFoo
{
    int foo();
}

// MyObject directly implements IBase:
struct MyObject : IBase, IFoo
{
    int foo() { return 0; }
}

// Generic extension that applies to all types that conforms to `IBase`:
extension<T:IBase> T : IFoo
{
    int foo() { return 1; }
}

int helper<T:IFoo>(T obj)
{
    return obj.foo();
}

int test()
{
    MyObject obj;

    // Returns 0, the conformance defined directly by the type
    // is preferred.
    return helper(obj);
}

This feature is similar to extension traits in Rust.

Variadic Generics

Slang supports variadic generic type parameters:

struct MyType<each T>
{}

Here each T defines a generic type pack parameter that can be a list of zero or more types. Therefore, the following instantiation of MyType is valid:

MyType // OK
MyType<int> // OK
MyType<int, float, void> // OK

A common use of variadic generics is to define printf:

void printf<each T>(String message, expand each T args) { ... }

The type syntax expand each T represents a expansion of the type pack T. Therefore, the type of args parameter is an expanded type pack. The expand expression can be thought of a map operation of a type pack. For example, give type pack T = int, float, bool, expand each T evaluates to the type pack of the same types, i.e. expand each T ==> int, float, bool. As a more interesting example, expand S<each T> will evaluate to S<int>, S<float>, S<bool>.

You can use expand expression on tuple or type-pack values to compute an expression for each element of the tuple or type pack. For example:

void printNumbers<each T>(expand each T args) where T == int
{
    // An single expression statement whose type will be `(void, void, ...)`.
    // where each `void` is the result of evaluating expression `printf(...)` with
    // each corresponding element in `args` passed as print operand.
    //
    expand printf("%d\n", each args);

    // The above statement is equivalent to:
    // ```
    // (printf("%d\n", args[0]), printf("%d\n", args[1]), ..., printf("%d\n", args[n-1]));
    // ```
}
void compute<each T>(expand each T args) where T == int
{
    // Maps every element in `args` to `elementValue + 1`, and forwards the
    // new values as arguments to `printNumbers`.
    printNumbers(expand (each args) + 1);

    // The above statement is equivalent to:
    // ```
    // printNumbers(args[0] + 1, args[1] + 1, ..., args[n-1] + 1);
    // ```
}
void test()
{
    compute(1,2,3);
    // Prints:
    // 2
    // 3
    // 4
}

As another example, you can use expand expression to sum up elements in a variadic argument pack:

void accumulateHelper(inout int dest, int value) { dest += value; }

void sum<each T>(expand each T args) where T == int
{
    int result = 0;
    expand accumulateHelper(result, each args);

    // The above statement is equivalent to:
    // ```
    // (accumulateHelper(result, args[0]), accumulateHelper(result, args[1]), ..., accumulateHelper(result, args[n-1]));
    // ```

    return result;
}

void test()
{
    int x = sum(1,2,3); // x == 6
}

Note that a variadic type pack parameter must appear at the end of a parameter list. If a generic type contains more than one type pack parameters, then each type pack must contain the same number of arguments at instantiation sites.

Builtin Interfaces

Slang supports the following builtin interfaces:

  • IComparable, provides methods for comparing two values of the conforming type. Supported by all basic data types, vector types and matrix types.
  • IRangedValue, provides methods for retrieving the minimum and maximum value expressed by the range of the type. Supported by all integer and floating-point scalar types.
  • IArithmetic, provides methods for the +, -, *, /, % and negating operations. Also provide a method for explicit conversion from int. Implemented by all builtin integer and floating-point scalar, vector and matrix types.
  • ILogical, provides methods for all bit operations and logical and, or, not operations. Also provide a method for explicit conversion from int. Implemented by all builtin integer scalar, vector and matrix types.
  • IInteger, represents a logical integer that supports both IArithmetic and ILogical operations. Implemented by all builtin integer scalar types.
  • IDifferentiable, represents a value that is differentiable.
  • IFloat, represents a logical float that supports both IArithmetic, ILogical and IDifferentiable operations. Also provides methods to convert to and from float. Implemented by all builtin floating-point scalar, vector and matrix types.
  • IArray<T>, represents a logical array that supports retrieving an element of type T from an index. Implemented by array types, vectors, matrices and StructuredBuffer.
  • IRWArray<T>, represents a logical array whose elements are mutable. Implemented by array types, vectors, matrices, RWStructuredBuffer and RasterizerOrderedStructuredBuffer.
  • IFunc<TResult, TParams...> represent a callable object (with operator()) that returns TResult and takes TParams... as argument.
  • IMutatingFunc<TResult, TParams...>, similar to IFunc, but the operator() method is [mutating].
  • IDifferentiableFunc<TResult, TParams...>, similar to IFunc, but the operator() method is [Differentiable].
  • IDifferentiableMutatingFunc<TResult, TParams...>, similar to IFunc, but the operator() method is [Differentiable] and [mutating].
  • __EnumType, implemented by all enum types.
  • __BuiltinIntegerType, implemented by all integer scalar types.
  • __BuiltinFloatingPointType, implemented by all floating-point scalar types.
  • __BuiltinArithmeticType, implemented by all integer and floating-point scalar types.
  • __BuiltinLogicalType, implemented by all integer types and the bool type.

Operator overloads are defined for IArithmetic, ILogical, IInteger, IFloat, __BuiltinIntegerType, __BuiltinFloatingPointType, __BuiltinArithmeticType and __BuiltinLogicalType types, so the following code is valid:

T f<T:IFloat>(T x, T y)
{
    if (x > T(0))
        return x + y;
    else
        return x - y;
}
void test()
{
    let rs = f(float3(4), float3(5)); // rs = float3(9,9,9)
}
1---
2layout: user-guide
3permalink: /user-guide/interfaces-generics
4---
5
6Interfaces and Generics
7===========================
8
9This chapter covers two interrelated Slang language features: interfaces and generics. We will talk about what they are, how they relate to similar features in other languages, how they are parsed and translated by the compiler, and show examples on how these features simplify and modularize shader code.
10
11Interfaces
12----------
13
14Interfaces are used to define the methods and services a type should provide. You can define a interface as the following example:
15```csharp
16interface IFoo
17{
18    int myMethod(float arg);
19}
20```
21
22Slang's syntax for defining interfaces are similar to `interface`s in C# and `protocol`s in Swift. In this example, the `IFoo` interface establishes a contract that any type conforming to this interface must provide a method named `myMethod` that accepts a `float` argument and returns an `int` value.
23
24A `struct` type may declare its conformance to an `interface` via the following syntax:
25```csharp
26struct MyType : IFoo
27{
28    int myMethod(float arg)
29    {
30        return (int)arg + 1;
31    }
32}
33```
34By declaring the conformance to `IFoo`, the definition of `MyType` must include a method named `myMethod` with a matching signature to that defined in the `IFoo` interface to satisfy the declared conformance. If a type misses any methods required by the interface, the Slang compiler will generate an error message.
35
36A `struct` type may declare multiple interface conformances:
37```csharp
38interface IBar { uint myMethod2(uint2 x); }
39
40struct MyType : IFoo, IBar
41{
42    int myMethod(float arg) {...}
43    uint myMethod2(uint2 x) {...}
44}
45```
46
47In this case, the definition of `MyType` must satisfy the requirements from both the `IFoo` and `IBar` interfaces by providing both the `myMethod` and `myMethod2` methods.
48
49Interface methods can have a default implementation, which will be used if a conforming type doesn't provide an overriding implementation. For example:
50
51```slang
52interface IFoo
53{
54    int getVal() { return 0; }
55}
56
57// OK, MyType.getVal() will use the default implementation provided in `IFoo`.
58struct MyType : IFoo {}
59```
60
61A concrete type that provides its overriding implementation to an interface method requirement that has a default implementation must be explicitly marked as 'override'. For example:
62
63```slang
64struct MyType2 : IFoo
65{
66    // Explicitly mark `getVal` as `override` is needed
67    // because `IFoo.getVal` has a body.
68    override int getVal() { return 1; }
69}
70```
71
72Generics
73---------------------
74
75Generics can be used to eliminate duplicate code for shared logic that operates on different types. The following example shows how to define a generic method in Slang.
76
77```csharp
78int myGenericMethod<T>(T arg) where T : IFoo
79{
80    return arg.myMethod(1.0);
81}
82```
83
84The above listing defines a generic method named `myGenericMethod`, which accepts an argument that can be of any type `T` as long as `T` conforms to the `IFoo` interface. The `T` here is called a _generic type parameter_, and it is associated with an _type constraint_ in the following `where` clause to indicate that any type represented by `T` must conform to the interface `IFoo`.
85
86The following listing shows how to invoke a generic method:
87```csharp
88MyType obj;
89int a = myGenericMethod<MyType>(obj); // OK, explicit type argument
90int b = myGenericMethod(obj); // OK, automatic type deduction
91```
92
93You may explicitly specify the concrete type to used for the generic type argument, by providing the types in angular brackets after the method name, or leave it to the compiler to automatically deduce the type from the argument list.
94
95Note that it is important to associate a generic type parameter with a type constraint. In the above example, although the definition of `myGenericMethod` is agnostic of the concrete type `T` will stand for, knowing that `T` conforms to `IFoo` allows the compiler to type-check and pre-compile `myGenericMethod` without needing to substitute `T` with any concrete types first. Similar to languages like C#, Rust, Swift and Java, leaving out the type constraint declaration on type parameter `T` will result in a compile error at the line calling `arg.myMethod` since the compiler cannot verify that `arg` has a member named `myMethod` without any knowledge on `T`. This is a major difference of Slang's generics compared to _templates_ in C++. 
96
97While C++ templates are a powerful language mechanism, Slang has followed the path of many other modern programming languages to adopt the more structural and restricted generics feature instead. This enables the Slang compiler to perform type checking early to give more readable error messages, and to speed-up compilation by reusing a lot of work for different instantiations of `myGenericMethod`.
98
99A generic parameter can also be a value. Currently, integer, bool and enum types are allowed as the type for a generic value parameter. Generic value parameters are declared with the `let` keyword. For example:
100
101```csharp
102void g1<let n : int>() { ... }
103
104enum MyEnum { A, B, C }
105void g2<let e : MyEnum>() { ... }
106
107void g3<let b : bool>() { ... }
108```
109
110### Alternative Syntax
111
112Alternatively, you can use `__generic` keyword to define generic parameters before the method:
113```csharp
114__generic<typename T> // `typename` is optional.
115int myGenericMethod(T arg) where T : IFoo
116{
117    return arg.myMethod(1.0);
118}
119```
120
121The same method can be defined in an alternative simplified syntax without the `where` clause:
122```csharp
123int myGenericMethod<T:IFoo>(T arg) { ... }
124```
125
126Generic value parameters can also be defined using the traditional C-style syntax:
127```csharp
128void g1<typename T, int n>() { ... }
129```
130
131Slang allows multiple `where` clauses, and multiple interface types in a single `where` clause:
132```csharp
133struct MyType<T, U>
134    where T: IFoo, IBar
135    where U : IBaz<T>
136{
137}
138// equivalent to:
139struct MyType<T, U>
140    where T: IFoo
141    where T : IBar
142    where U : IBaz<T>
143{
144}
145```
146
147Optional conformances can be expressed compactly using the `where optional` syntax:
148```csharp
149// Together, these two overloads...
150int myGenericMethod<T>(T arg)
151{
152}
153
154int myGenericMethod<T>(T arg) where T: IFoo
155{
156    arg.myMethod(1.0);
157}
158
159// ... are equivalent to:
160int myGenericMethod<T>(T arg) where optional T: IFoo
161{
162    if (T is IFoo)
163    {
164        arg.myMethod(1.0); // OK in a block that checks for T: IFoo conformance.
165    }
166}
167```
168
169Supported Constructs in Interface Definitions
170-----------------------------------------------------
171
172Slang supports many other constructs in addition to ordinary methods as a part of an interface definition.
173
174### Properties
175
176```csharp
177interface IFoo
178{
179    property int count {get; set;}
180}
181```
182The above listing declares that any conforming type must define a property named `count` with both a `getter` and a `setter` method.
183
184### Generic Methods
185
186```csharp
187interface IFoo
188{
189    int compute<T>(T val) where T : IBar;
190}
191```
192The above listing declares that any conforming type must define a generic method named `compute` that has one generic type parameter conforming to the `IBar` interface.
193
194### Static Methods
195
196```csharp
197interface IFoo
198{
199    static int compute(int val);
200};
201```
202
203The above listing declares that any conforming type must define a static method named `compute`. This allows the following generic method to pass type-checking:
204```csharp
205void f<T>() where T : IFoo
206{
207    T.compute(5); // OK, T has a static method `compute`.
208}
209```
210
211### Static Constants
212
213You can define static constant requirements in an interface. The constants can be accessed in places where a compile-time constant is needed.
214```csharp
215interface IMyValue
216{
217    static const int value;
218}
219struct MyObject2 : IMyValue
220{
221    static const int value = 2;
222}
223struct GetValuePlus1<T:IMyValue>
224{
225    static const int value = T.value + 1;
226}
227
228static const int result = GetValuePlus1<MyObject2>.value;  // result == 3
229```
230
231### `This` Type
232
233You may use a special keyword `This` in interface definitions to refer to the type that is conforming to the interface. The following examples demonstrate a use of `This` type:
234```csharp
235interface IComparable
236{
237    int comparesTo(This other);
238}
239struct MyObject : IComparable
240{
241    int val;
242    int comparesTo(MyObject other)
243    {
244        return val < other.val ? -1 : 1;
245    }
246}
247```
248In this example, the `IComparable` interface declares that any conforming type must provide a `comparesTo` method that performs a comparison between an object to another object of the same type. The `MyObject` type satisfies this requirement by providing a `comparesTo` method that accepts a `MyObject` typed argument, since in the scope of `MyObject`, `This` type is equivalent to `MyObject`.
249
250### Initializers
251
252Consider a generic method that wants to create and initialize a new instance of generic type `T`:
253```csharp
254void f<T:IFoo>()
255{
256    T obj = /*a newly initialized T*/
257}
258```
259One way to implement this is to introduce a static method requirement in `IFoo`:
260```csharp
261interface IFoo
262{
263    static This create();
264}
265```
266With this interface definition, we can define `f` as following:
267```csharp
268void f<T:IFoo>()
269{
270    T obj = T.create();
271}
272```
273
274This solution works just fine, but it would be nicer if you can just write:
275```csharp
276T obj = T();
277```
278Or simply
279```csharp
280T obj;
281```
282And let the compiler invoke the default initializer defined in the type.
283To enable this, you can include an initializer requirement in the interface definition:
284```csharp
285interface IFoo
286{
287    __init();
288}
289```
290
291Initializers with parameters are supported as well. For example:
292```csharp
293interface IFoo
294{
295    __init(int a, int b);
296}
297void g<T:IFoo>()
298{
299    T obj = {1, 2}; // OK, invoking the initializer on T.
300}
301```
302
303Associated Types
304-------------------------
305
306When writing code using interfaces and generics, there are some situations where an interface method needs to return an object whose type is implementation-dependent. For example, consider the following `IFloatContainer` interface that represents a container of `float` values:
307```csharp
308// Represents a container of float values.
309interface IFloatContainer
310{
311    // Returns the number of elements in this container.
312    uint getCount();
313    // Returns an iterator representing the start of the container.
314    Iterator begin();
315    // Returns an iterator representing the end of the container.
316    Iterator end();
317    // Return the element at the location represented by `iter`.
318    float getElementAt(Iterator iter);
319}
320```
321An implementation of the `IFloatContainer` interface may use different types of iterators. For example, an implementation that is simply an array of `float`s can expose `Iterator` as a simple integer index:
322```csharp
323struct ArrayFloatContainer : IFloatContainer
324{
325    float content[10];
326    uint getCount() { return 10; }
327    uint begin() { return 0; }
328    uint end() { return 10; }
329    float getElementAt(uint iter) { return content[iter]; }
330}
331```
332On the other hand, an implementation that uses multiple buffers as the backing storage may use a more complex type to locate an element:
333```csharp
334// Exposes values in two `StructuredBuffer`s as a single container.
335struct MultiArrayFloatContainer : IFloatContainer
336{
337    StructuredBuffer<float> firstBuffer;
338    StructuredBuffer<float> secondBuffer;
339    uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); }
340
341    // `uint2.x` indicates which buffer, `uint2.y` indicates the index within the buffer.
342    uint2 begin() { return uint2(0,0); }
343    uint2 end() { return uint2 (1, getBufferSize(secondBuffer)); }
344    float getElementAt(uint2 iter)
345    {
346        if (iter.x == 0) return firstBuffer[iter.y];
347        else return secondBuffer[iter.y];
348    }
349}
350```
351
352Ideally, a generic function that wishes to enumerate values in a `IFloatContainer` shouldn't need to care about the implementation details on what the concrete type of `Iterator` is, and we would like to be able to write the following:
353```csharp
354float sum<T:IFloatContainer>(T container)
355{
356    float result = 0.0f;
357    for (T.Iterator iter = container.begin(); iter != container.end(); iter=iter.next())
358    {
359        float val = container.getElementAt(iter);
360        result += val;
361    }
362    return result;
363}
364```
365Here the `sum` function simply wants to access all the elements and sum them up. The details of what the `Iterator` type actually is does not matter to the definition of `sum`.
366
367The problem is that the `IFloatContainer` interface definition requires methods like `begin()`, `end()` and `getElementAt()` to refer to a iterator type that is implementation dependent. How should the signature of these methods be defined in the interface? The answer is to use _associated types_.
368
369In addition to constructs listed in the previous section, Slang also supports defining associated types in an `interface` definition. An associated type can be defined as following.
370```csharp
371// The interface for an iterator type.
372interface IIterator
373{
374    // An iterator needs to know how to move to the next element.
375    This next();
376}
377
378interface IFloatContainer
379{
380    // Requires an implementation to define a typed named `Iterator` that
381    // conforms to the `IIterator` interface.
382    associatedtype Iterator : IIterator;
383
384    // Returns the number of elements in this container.
385    uint getCount();
386    // Returns an iterator representing the start of the container.
387    Iterator begin();
388    // Returns an iterator representing the end of the container.
389    Iterator end();
390    // Return the element at the location represented by `iter`.
391    float getElementAt(Iterator iter);
392};
393```
394
395This `associatedtype` definition in `IFloatContainer` requires that all types conforming to this interface must also define a type in its scope named `Iterator`, and this iterator type must conform to the `IIterator` interface. An implementation to the `IFloatContainer` interface by using either a `typedef` declaration or a `struct` definition inside its scope to satisfy the associated type requirement. For example, the `ArrayFloatContainer` can be implemented as following:
396```csharp
397struct ArrayIterator : IIterator
398{
399    uint index;
400    __init(int x) { index = x; }
401    ArrayIterator next()
402    {
403        return ArrayIterator(index + 1);
404    }
405}
406struct ArrayFloatContainer : IFloatContainer
407{
408    float content[10];
409
410    // Specify that the associated `Iterator` type is `ArrayIterator`.
411    typedef ArrayIterator Iterator;
412
413    Iterator getCount() { return 10; }
414    Iterator begin() { return ArrayIterator(0); }
415    Iterator end() { return ArrayIterator(10); }
416    float getElementAt(Iterator iter) { return content[iter.index]; }
417}
418```
419
420Alternatively, you may also define the `Iterator` type directly inside a `struct` implementation, as in the following definition for `MultiArrayFloatContainer`:
421```csharp
422// Exposes values in two `StructuredBuffer`s as a single container.
423struct MultiArrayFloatContainer : IFloatContainer
424{
425    // Represents an iterator of this container
426    struct Iterator : IIterator
427    {
428        // `index.x` indicates which buffer the element is located in.
429        // `index.y` indicates which the index of the element inside the buffer.
430        uint2 index;
431
432        // We also need to keep a size of the first buffer so we know when to
433        // switch to the second buffer.
434        uint firstBufferSize;
435
436        // Implementation of IIterator.next()
437        Iterator next()
438        {
439            Iterator result;
440            result.index.x = index.x;
441            result.index.y = index.y + 1;
442            // If we are at the end of the first buffer,
443            // move to the head of the second buffer
444            if (result.index.x == 0 && result.index.y == firstBufferSize)
445            {
446                result.index = uint2(1, 0);
447            }
448            return result;
449        }
450    }
451
452    StructuredBuffer<float> firstBuffer;
453    StructuredBuffer<float> secondBuffer;
454    uint getCount() { return getBufferSize(firstBuffer) + getBufferSize(secondBuffer); }
455
456    Iterator begin()
457    {
458        Iterator iter;
459        iter.index = uint2(0, 0);
460        iter.firstBufferSize = getBufferSize(firstBuffer);
461        return iter;
462    }
463    Iterator end()
464    {
465        Iterator iter;
466        iter.index = uint2(1, getBufferSize(secondBuffer));
467        iter.firstBufferSize = 0;
468        return iter;
469    }
470    float getElementAt(Iterator iter)
471    {
472        if (iter.index.x == 0) return firstBuffer[iter.index.y];
473        else return secondBuffer[iter.index.y];
474    }
475}
476```
477
478In summary, an `associatedtype` requirement in an interface is similar to other types of requirements: a method requirement means that an implementation must provide a method matching the interface signature, while an `associatedtype` requirement means that an implementation must provide a type in its scope with the matching name and interface constraint. In general, when defining an interface that is producing and consuming an object whose actual type is implementation-dependent, the type of this object can often be modeled as an associated type in the interface.
479
480
481### Comparing Generics to C++ Templates
482Readers who are familiar with C++ could easily relate the `Iterator` example in previous subsection to the implementation of STL. In C++, the `sum` function can be easily written with templates:
483```C++
484template<typename TContainer>
485float sum(const TContainer& container)
486{
487    float result = 0.0f;
488    // Assumes `TContainer` has a type `Iterator` that supports `operator++`.
489    for (TContainer::Iterator iter = container.begin(); iter != container.end(); ++iter)
490    {
491        result += container.getElementAt(iter);
492    }
493    return result;
494}
495```
496
497A C++ programmer can implement `ArrayFloatContainer` as following:
498```C++
499struct ArrayFloatContainer
500{
501    float content[10];
502
503    typedef uint32_t Iterator;
504
505    Iterator getCount() { return 10; }
506    Iterator begin() { return 0; }
507    Iterator end() { return 10; }
508    float getElementAt(Iterator iter) { return content[iter]; }
509};
510```
511Because C++ does not require a template function to define _constraints_ on the templated type, there are no interfaces or inheritances involved in the definition of `ArrayFloatContainer`. However `ArrayFloatContainer` still needs to define what its `Iterator` type is, so the `sum` function can be successfully specialized with an `ArrayFloatContainer`.
512
513Note that the biggest difference between C++ templates and generics is that templates are not type-checked prior to specialization, and therefore the code that consumes a templated type (`TContainer` in this example) can simply assume `container` has a method named `getElementAt`, and the `TContainer` scope provides a type definition for `TContainer::Iterator`. Compiler error only arises when the programmer is attempting to specialize the `sum` function with a type that does not meet these assumptions. Contrarily, Slang requires all possible uses of a generic type be declared through an interface. By stating that `TContainer:IContainer` in the generics declaration, the Slang compiler can verify that `container.getElementAt` is calling a valid function. Similarly, the interface also tells the compiler that `TContainer.Iterator` is a valid type and enables the compiler to fully type check the `sum` function without specializing it first.
514
515### Similarity to Swift and Rust
516
517Slang's `associatedtype` shares the same semantic meaning with `associatedtype` in a Swift `protocol` or `type` in a Rust `trait`, except that Slang currently does not support the more general `where` clause in these languages. C# does not have an equivalent to `associatedtype`, and programmers need to resort to generic interfaces to achieve similar goals.
518
519Generic Value Parameters
520-------------------------------
521
522So far we have demonstrated generics with _type parameters_. Additionally, Slang also supports generic _value_ parameters.
523The following listing shows an example of generic value parameters.
524```csharp
525struct Array<T, let N : int>
526{
527    T arrayContent[N];
528}
529```
530In this example, the `Array` type has a generic type parameter, `T`, that is used as the element type of the `arrayContent` array, and a generic value parameter `N` of integer type.
531
532Note that the builtin `vector<float, N>` type also has an generic value parameter `N`.
533
534> #### Note ####
535> The only type of generic value parameters are `int`, `uint` and `bool`. `float` and
536> other types cannot be used in a generic value parameter. Computations in a type
537> expression are supported as long as they can be evaluated at compile time. For example,
538`vector<float, 1+1>` is allowed and considered equivalent to `vector<float, 2>`.
539
540
541Type Equality Constraints
542-------------------------
543
544In addition to type conformance constraints as in `where T : IFoo`, Slang also supports type equality constraints. This is mostly useful in specifying additional constraints for
545associated types. For example:
546```csharp
547interface IFoo { associatedtype A; }
548
549// Access all T that conforms to IFoo, and T.A is `int`.
550void foo<T>(T v)
551    where T : IFoo
552    where T.A == int
553{
554}
555
556struct X : IFoo
557{
558    typealias A = int;
559}
560
561struct Y : IFoo
562{
563    typealias A = float;
564}
565
566void test()
567{
568    foo<X>(X()); // OK
569    foo<Y>(Y()); // Error, `Y` cannot be used for `T`.
570}
571```
572
573Interface-typed Values
574-------------------------------
575
576So far we have been using interfaces as constraints to generic type parameters. For example, the following listing defines a generic function with a type parameter `TTransform` constrained by interface `ITransform`:
577
578```csharp
579interface ITransform
580{
581    int compute(MyObject obj);
582}
583
584// Defining a generic method:
585int apply<TTransform : ITransform>(TTransform transform, MyObject object)
586{
587    return transform.compute(object);
588}
589```
590
591While Slang's syntax for defining generic methods bears similarity to generics in C#/Java and templates in C++ and should be easy to users who are familiar with these languages, codebases that make heavy use of generics can quickly become verbose and difficult to read. To reduce the amount of boilerplate, Slang supports an alternate way to define the `apply` method by using the interface type `ITransform` as parameter type directly:
592
593```csharp
594// A method that is equivalent to `apply` but uses simpler syntax:
595int apply_simple(ITransform transform, MyObject object)
596{
597    return transform.compute(object);
598}
599```
600
601Instead of defining a generic type parameter `TTransform` and a method parameter `transform` that has `TTransform` type, you can simply define the same `apply` function like a normal method, with a `transform` parameter whose type is an interface. From the Slang compiler's view, `apply` and `apply_simple` will be compiled to the same target code.
602
603In addition to parameters, Slang allows variables, and function return values to have an interface type as well:
604```csharp
605ITransform test(ITransform arg)
606{
607    ITransform v = arg;
608    return v;
609}
610```
611
612### Restrictions and Caveats
613
614The Slang compiler always attempts to determine the actual type of an interface-typed value at compile time and specialize the code with the actual type. As long as the compiler can successfully determine the actual type, code that uses interface-typed values are equivalent to code written in the generics syntax. However, when interface types are used in function return values, the compiler will not be able to trivially propagate type information. For example:
615```csharp
616ITransform getTransform(int x)
617{
618    if (x == 0)
619    {
620        Type1Transform rs = {};
621        return rs;
622    }
623    else
624    {
625        Type2Transform rs = {};
626        return rs;
627    }
628}
629```
630In this example, the actual type of the return value is dependent on the value of `x`, which may not be known at compile time. This means that the concrete type of the return value at invocation sites of `getTransform` may not be statically determinable. When the Slang compiler cannot infer the concrete type of an interface-type value, it will generate code that performs a dynamic dispatch based on the concrete type of the value at runtime, which may introduce performance overhead. Note that this behavior applies to function return values in the form of `out` parameters as well:
631
632```csharp
633void getTransform(int x, out ITransform transform)
634{
635    if (x == 0)
636    {
637        Type1Transform rs = {};
638        transform = rs;
639    }
640    else
641    {
642        Type2Transform rs = {};
643        transform = rs;
644    }
645}
646```
647This `getTransform` definition can also result in dynamic dispatch code since the type of `transform` may not be statically determinable.
648
649When the compiler is generating dynamic dispatch code for interface-typed values, it requires the concrete type of the interface-typed value to be free of any opaque-typed fields (e.g. resources and buffer types). A compiler error will generated upon such attempts:
650```csharp
651struct MyTransform : ITransform
652{
653    StructuredBuffer<int> buffer;
654    int compute(MyObject obj)
655    {
656        return buffer[0];
657    }
658}
659
660ITransform getTransform(int x)
661{
662    MyTransform rs;
663    // Error: cannot use an opaque value as an interface-typed return value.
664    return rs;
665}
666```
667
668Assigning different values to a mutable interface-typed variable also undermines the compiler's ability to statically determine the type of the variable, and is not supported by the Slang compiler today:
669```csharp
670void test(int x)
671{
672    ITransform t = Type1Transform();
673    // Do something ...
674    // Assign a different type of transform to `t`:
675    // (Not supported by Slang today)
676    t = Type2Transform();
677    // Do something else...
678}
679```
680
681In general, if the use of interface-typed values is restricted to function parameters only, then the all code that involves interface-typed values will be compiled the same way as if the code is written using standard generics syntax.
682
683
684Extending a Type with Additional Interface Conformances
685-----------------------------
686In the previous chapter, we introduced the `extension` feature that lets you define new members to an existing type in a separate location outside the original definition of the type. 
687
688`extensions` can be used to make an existing type conform to additional interfaces. Suppose we have an interface `IFoo` and a type `MyObject` that implements the interface:
689
690```csharp
691interface IFoo
692{
693    int foo();
694};
695
696struct MyObject : IFoo
697{
698    int foo() { return 0; }
699}
700```
701
702Now we introduce another interface, `IBar`:
703```csharp
704interface IBar
705{
706    float bar();
707}
708```
709
710We can define an `extension` to make `MyObject` conform to `IBar` as well:
711```csharp
712extension MyObject : IBar
713{
714    float bar() { return 1.0f }
715}
716```
717
718With this extension, we can use `MyObject` in places that expects an `IBar` as well:
719```csharp
720void use(IBar b)
721{
722    b.bar();
723}
724
725void test()
726{
727    MyObject obj;
728    use(obj); // OK, `MyObject` is extended to conform to `IBar`.
729}
730```
731
732You may define more than one interface conformances in a single `extension`:
733```csharp
734interface IBar2
735{
736    float bar2();
737}
738extension MyObject : IBar, IBar2
739{
740    float bar() { return 1.0f }
741    float bar2() { return 2.0f }
742}
743```
744
745`is` and `as` Operator
746----------------------------
747
748You can use `is` operator to test if an interface-typed value is of a specific concrete type, and use `as` operator to downcast the value into a specific type.
749The `as` operator returns an `Optional<T>` that is not `none` if the downcast succeeds.
750
751```csharp
752interface IFoo
753{
754    int foo();
755}
756struct MyImpl : IFoo
757{
758    int foo() { return 0; }
759}
760void test(IFoo foo)
761{
762    bool t = foo is MyImpl; // true
763    Optional<MyImpl> optV = foo as MyImpl;
764    if (t == (optV != none))
765        printf("success");
766    else
767        printf("fail");
768}
769void main()
770{
771    MyImpl v;
772    test(v);
773}
774// Result:
775// "success"
776```
777
778In addition to casting from an interface type to a concrete type, `as` and `is` operator can be used on generic types as well to cast a generic type into a concrete type. For example:
779```csharp
780T compute<T>(T a1, T a2)
781{
782    if (a1 is float)
783    {
784        return reinterpret<T>((a1 as float).value + (a2 as float).value);
785    }
786    else if (T is int)
787    {
788        return reinterpret<T>((a1 as int).value - (a2 as int).value);
789    }
790    return T();
791}
792// compute(1.0f, 2.0f) == 3.0f
793// compute(3, 1) == 2
794```
795
796Since `as` operator returns a `Optional<T>` type, it can also be used in the `if` predicate to test if an object can be
797casted to a specific type, once the cast test is successful, the object can be used in the `if` block as the casted type
798without the need to retrieve the `Optional<T>::value` property, for example:
799
800```csharp
801interface IFoo
802{
803    void foo();
804}
805
806struct MyImpl1 : IFoo
807{
808    void foo() { printf("MyImpl1");}
809}
810
811struct MyImpl2 : IFoo
812{
813    void foo() { printf("MyImpl2");}
814}
815
816struct MyImpl3 : IFoo
817{
818    void foo() { printf("MyImpl3");}
819}
820
821void test(IFoo foo)
822{
823    // This syntax will be desugared to the following:
824    // {
825    //      Optional<MyImpl1> optVar = foo as MyImpl1;
826    //      if (optVar.hasValue)
827    //      {
828    //          MyImpl1 t = optVar.value;
829    //          t.foo();
830    //      }
831    //      else if ...
832    // }
833    if (let t = foo as MyImpl1) // t is of type MyImpl1
834    {
835        t.foo();
836    }
837    else if (let t = foo as MyImpl2) // t is of type MyImpl2
838    {
839        t.foo();
840    }
841    else
842        printf("fail");
843}
844
845void main()
846{
847    MyImpl1 v1;
848    test(v1);
849
850    MyImpl2 v2;
851    test(v2);
852}
853
854```
855See  [if-let syntax](03-convenience-features.md#if_let-syntax) for more details.
856
857
858Generic Interfaces
859------------------
860
861Slang allows interfaces themselves to be generic. A common use of generic interfaces is to define the `IEnumerable` type:
862```csharp
863interface IEnumerator<T>
864{
865    This moveNext();
866    bool isEnd();
867    T getValue();
868}
869
870interface IEnumerable<T>
871{
872    associatedtype Enumerator : IEnumerator<T>;
873    Enumerator getEnumerator();
874}
875```
876
877You can constrain a generic type parameter to conform to a generic interface:
878```csharp
879void traverse<TElement, TCollection>(TCollection c)
880    where TCollection : IEnumerable<TElement>
881{
882    ...
883}
884```
885
886
887Generic Extensions
888----------------------
889You can use generic extensions to extend a generic type. For example,
890```csharp
891interface IFoo { void foo(); }
892interface IBar { void bar(); }
893
894struct MyType<T : IFoo>
895{
896    void foo() { ... }
897}
898
899// Extend `MyType<T>` so it conforms to `IBar`.
900extension<T:IFoo> MyType<T> : IBar
901{
902    void bar() { ... }
903}
904// Equivalent to:
905__generic<T:IFoo>
906extension MyType<T> : IBar
907{
908    void bar() { ... }
909}
910```
911
912
913Extensions to Interfaces
914-----------------------------
915
916In addition to extending ordinary types, you can define extensions on all types that conforms to some interface:
917
918```csharp
919// An example interface.
920interface IFoo
921{
922    int foo();
923}
924
925// Extend any type `T` that conforms to `IFoo` with a `bar` method.
926extension<T:IFoo> T
927{
928    int bar() { return 0; }
929}
930
931int use(IFoo foo)
932{
933    // With the extension, all uses of `IFoo` typed values
934    // can assume there is a `bar` method.
935    return foo.bar();
936}
937```
938
939Note that `interface` types cannot be extended, because extending an `interface` with new requirements would make all existing types that conforms
940to the interface no longer valid.
941
942In the presence of extensions, it is possible for a type to have multiple ways to 
943conform to an interface. In this case, Slang will always prefer the more specific conformance
944over the generic one. For example, the following code illustrates this behavior:
945
946```csharp
947interface IBase{}
948interface IFoo
949{
950    int foo();
951}
952
953// MyObject directly implements IBase:
954struct MyObject : IBase, IFoo
955{
956    int foo() { return 0; }
957}
958
959// Generic extension that applies to all types that conforms to `IBase`:
960extension<T:IBase> T : IFoo
961{
962    int foo() { return 1; }
963}
964
965int helper<T:IFoo>(T obj)
966{
967    return obj.foo();
968}
969
970int test()
971{
972    MyObject obj;
973
974    // Returns 0, the conformance defined directly by the type
975    // is preferred.
976    return helper(obj);
977}
978```
979
980This feature is similar to extension traits in Rust.
981
982
983Variadic Generics
984-------------------------
985
986Slang supports variadic generic type parameters:
987```csharp
988struct MyType<each T>
989{}
990```
991
992Here `each T` defines a generic type pack parameter that can be a list of zero or more types. Therefore, the following instantiation of `MyType` is valid:
993```
994MyType // OK
995MyType<int> // OK
996MyType<int, float, void> // OK
997```
998
999A common use of variadic generics is to define `printf`:
1000```csharp
1001void printf<each T>(String message, expand each T args) { ... }
1002```
1003
1004The type syntax `expand each T` represents a expansion of the type pack `T`. Therefore, the type of `args` parameter is an expanded type pack.
1005The `expand` expression can be thought of a map operation of a type pack. For example,
1006give type pack `T = int, float, bool`, `expand each T` evaluates to the type pack of the same types, i.e. `expand each T ==> int, float, bool`.
1007As a more interesting example, `expand S<each T>` will evaluate to `S<int>, S<float>, S<bool>`.
1008
1009You can use `expand` expression on tuple or type-pack values to compute an expression for each element of the tuple or type pack.
1010For example:
1011
1012```csharp
1013void printNumbers<each T>(expand each T args) where T == int
1014{
1015    // An single expression statement whose type will be `(void, void, ...)`.
1016    // where each `void` is the result of evaluating expression `printf(...)` with
1017    // each corresponding element in `args` passed as print operand.
1018    //
1019    expand printf("%d\n", each args);
1020
1021    // The above statement is equivalent to:
1022    // ```
1023    // (printf("%d\n", args[0]), printf("%d\n", args[1]), ..., printf("%d\n", args[n-1]));
1024    // ```
1025}
1026void compute<each T>(expand each T args) where T == int
1027{
1028    // Maps every element in `args` to `elementValue + 1`, and forwards the
1029    // new values as arguments to `printNumbers`.
1030    printNumbers(expand (each args) + 1);
1031
1032    // The above statement is equivalent to:
1033    // ```
1034    // printNumbers(args[0] + 1, args[1] + 1, ..., args[n-1] + 1);
1035    // ```
1036}
1037void test()
1038{
1039    compute(1,2,3);
1040    // Prints:
1041    // 2
1042    // 3
1043    // 4
1044}
1045```
1046
1047As another example, you can use `expand` expression to sum up elements in a variadic argument pack:
1048```csharp
1049void accumulateHelper(inout int dest, int value) { dest += value; }
1050
1051void sum<each T>(expand each T args) where T == int
1052{
1053    int result = 0;
1054    expand accumulateHelper(result, each args);
1055
1056    // The above statement is equivalent to:
1057    // ```
1058    // (accumulateHelper(result, args[0]), accumulateHelper(result, args[1]), ..., accumulateHelper(result, args[n-1]));
1059    // ```
1060
1061    return result;
1062}
1063
1064void test()
1065{
1066    int x = sum(1,2,3); // x == 6
1067}
1068```
1069
1070Note that a variadic type pack parameter must appear at the end of a parameter list. If a generic type contains more than one
1071type pack parameters, then each type pack must contain the same number of arguments at instantiation sites.
1072
1073Builtin Interfaces
1074-----------------------------
1075
1076Slang supports the following builtin interfaces:
1077
1078- `IComparable`, provides methods for comparing two values of the conforming type. Supported by all basic data types, vector types and matrix types.
1079- `IRangedValue`, provides methods for retrieving the minimum and maximum value expressed by the range of the type. Supported by all integer and floating-point scalar types.
1080- `IArithmetic`, provides methods for the `+`, `-`, `*`, `/`, `%` and negating operations. Also provide a method for explicit conversion from `int`. Implemented by all builtin integer and floating-point scalar, vector and matrix types.
1081- `ILogical`, provides methods for all bit operations and logical `and`, `or`, `not` operations. Also provide a method for explicit conversion from `int`. Implemented by all builtin integer scalar, vector and matrix types.
1082- `IInteger`, represents a logical integer that supports both `IArithmetic` and `ILogical` operations. Implemented by all builtin integer scalar types.
1083- `IDifferentiable`, represents a value that is differentiable.
1084- `IFloat`, represents a logical float that supports both `IArithmetic`, `ILogical` and `IDifferentiable` operations. Also provides methods to convert to and from `float`. Implemented by all builtin floating-point scalar, vector and matrix types.
1085- `IArray<T>`, represents a logical array that supports retrieving an element of type `T` from an index. Implemented by array types, vectors, matrices and `StructuredBuffer`.
1086- `IRWArray<T>`, represents a logical array whose elements are mutable. Implemented by array types, vectors, matrices, `RWStructuredBuffer` and `RasterizerOrderedStructuredBuffer`.
1087- `IFunc<TResult, TParams...>` represent a callable object (with `operator()`) that returns `TResult` and takes `TParams...` as argument.
1088- `IMutatingFunc<TResult, TParams...>`, similar to `IFunc`, but the `operator()` method is `[mutating]`.
1089- `IDifferentiableFunc<TResult, TParams...>`, similar to `IFunc`, but the `operator()` method is `[Differentiable]`.
1090- `IDifferentiableMutatingFunc<TResult, TParams...>`, similar to `IFunc,` but the `operator()` method is `[Differentiable]` and `[mutating]`.
1091- `__EnumType`, implemented by all enum types.
1092- `__BuiltinIntegerType`, implemented by all integer scalar types.
1093- `__BuiltinFloatingPointType`, implemented by all floating-point scalar types.
1094- `__BuiltinArithmeticType`, implemented by all integer and floating-point scalar types.
1095- `__BuiltinLogicalType`, implemented by all integer types and the `bool` type.
1096
1097Operator overloads are defined for `IArithmetic`, `ILogical`, `IInteger`, `IFloat`, `__BuiltinIntegerType`, `__BuiltinFloatingPointType`,  `__BuiltinArithmeticType` and `__BuiltinLogicalType` types, so the following code is valid:
1098
1099```csharp
1100T f<T:IFloat>(T x, T y)
1101{
1102    if (x > T(0))
1103        return x + y;
1104    else
1105        return x - y;
1106}
1107void test()
1108{
1109    let rs = f(float3(4), float3(5)); // rs = float3(9,9,9)
1110}
1111```