|
| 1 | +# SP#030: Default Implementation of Interface Methods |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Status: In Experiment |
| 6 | + |
| 7 | +Implementation: [PR 7439](https://github.com/shader-slang/slang/pull/7439), [PR 7458](https://github.com/shader-slang/slang/pull/7458), [PR 7465](https://github.com/shader-slang/slang/pull/7465) |
| 8 | + |
| 9 | +Author: Yong He |
| 10 | + |
| 11 | +Reviewer: Theresa Foley |
| 12 | + |
| 13 | +## Background |
| 14 | + |
| 15 | +Often times, an interface method is implemented trivially for all but a few conforming types. This arises most often when an existing interface |
| 16 | +is extended to support an additional feature that is only provided or meaningful for a new type that conforms to the interface. Currently, a |
| 17 | +developer must manually duplicate the trivial implementation for all existing types after extending the interface, which is a laborious process. |
| 18 | + |
| 19 | +This proposal is to allow interface methods to have a default implementation, that will be used by a conforming type to satisfy the interface |
| 20 | +requirement, if the conforming type does not provide its own implementation of the method. Note that other types of requirements, such as properties, subscript operators, associated types and associated constants are not covered by this proposal and default implementation for these types of requirements will remain unsupported. |
| 21 | + |
| 22 | +## Proposed Solution |
| 23 | + |
| 24 | +We propose to allow interface methods to have a body that serves as the default implementation: |
| 25 | + |
| 26 | +```slang |
| 27 | +interface IFoo |
| 28 | +{ |
| 29 | + int getVal() |
| 30 | + { |
| 31 | + return 0; |
| 32 | + } |
| 33 | +} |
| 34 | +
|
| 35 | +struct Impl : IFoo |
| 36 | +{ |
| 37 | + // OK, using IFoo.getVal() to satisfy the requirement. |
| 38 | +} |
| 39 | +
|
| 40 | +struct Impl2 : IFoo |
| 41 | +{ |
| 42 | + // OK, overriding the default implementation. |
| 43 | + override int getVal() { return 1; } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Both static and non-static methods can have default implementations. |
| 48 | + |
| 49 | +A default implementation can be marked as `[Differentiable]` for automatic differentiation. |
| 50 | + |
| 51 | +Default implementations is treated as if it is an ordinary function generic on `This` type. The `IFoo` interface above will |
| 52 | +be interpreted by the compiler as: |
| 53 | + |
| 54 | +```slang |
| 55 | +interface IFoo |
| 56 | +{ |
| 57 | + [HasDefaultImpl(IFoo_getVal_defaultImpl)] |
| 58 | + int getVal(); // the requirement. |
| 59 | +} |
| 60 | +
|
| 61 | +// pseudo syntax: |
| 62 | +__generic<This:IFoo> |
| 63 | +int IFoo_getVal_defaultImpl(this:This) |
| 64 | +{ |
| 65 | + return 0; |
| 66 | +} |
| 67 | +
|
| 68 | +struct Impl : IFoo |
| 69 | +{ |
| 70 | + .getVal := IFoo_getVal_defaultImpl<Impl>; |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +Note that a method in a confirming type that overrides the default implementation in the interface must be explicitly marked as `override`. Overriding a function that is not declared in any base interfaces is an error. |
| 75 | + |
| 76 | +### `override` in Interfaces |
| 77 | + |
| 78 | +A method with a default implementation in an interface cannot be marked `override`, and will not |
| 79 | +override any requirements in the base interfaces. Consider the following: |
| 80 | + |
| 81 | +```slang |
| 82 | +interface IBase |
| 83 | +{ |
| 84 | + int getVal() { return 0; } |
| 85 | +} |
| 86 | +interface IDerived : IBase |
| 87 | +{ |
| 88 | + int getVal() { return 1; } |
| 89 | +} |
| 90 | +sttruct Impl : IDerived {} |
| 91 | +
|
| 92 | +int testBase<T:IBase>(T v) |
| 93 | +{ |
| 94 | + return v.getVal(); |
| 95 | +} |
| 96 | +void main() |
| 97 | +{ |
| 98 | + Impl impl = {}; |
| 99 | + printf("%d\n", testBase(impl)); |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +Should the output be `0` (calling `IBase.getVal`) or `1` (calling `IDerived.getVal`)? Both behaviors are reasonable. |
| 104 | +For simplicity, the current decision is to treat `IBase.getVal` and |
| 105 | +`IDerived.getVal` as distinct requirements, so calling `testBase(impl)` should return `0`, and calling `testDerived(impl)` should return `1`. |
| 106 | +That is, the existence of `IDerived.getVal` will not override the requirement from `IBase.getVal`. |
| 107 | + |
| 108 | +Using `override` keyword on an interface method is considered an error. |
| 109 | + |
| 110 | +### Constructors |
| 111 | + |
| 112 | +For simplicity, constructors/initializers are not allowed to have a defualt implementation in interfaces. |
| 113 | + |
| 114 | +### Storage Accessors |
| 115 | + |
| 116 | +`get`, `set` and other storage accessors for properties or subscript operations are not allowed to have a default implementation in interfaces. |
| 117 | + |
| 118 | +## Related Work |
| 119 | + |
| 120 | +Default implementations are proven to be a useful feature and are commonly supported in modern languages. They are allowed in Rust traits Swift protocols, and C# interfaces (since C# 8.0). |
0 commit comments