Structures
Syntax
Struct no-body declaration:
struct-decl=
[modifier-list]
'struct'[identifier] [generic-params-decl]
[':'bases-clause] ['='simple-type-spec]';'
Struct with-members declaration:
struct-decl=
[modifier-list]
'struct'[identifier] [generic-params-decl]
[':'bases-clause]
('where'where-clause)*
'{'member-list'}'
Struct link-time extern type declaration:
struct-decl=
[modifier-list]
'extern''struct'[identifier] [generic-params-decl]
[':'bases-clause] ['='simple-type-spec]';'
Struct link-time export type alias declaration:
struct-decl=
[modifier-list]
'export''struct'[identifier] [generic-params-decl]
[':'bases-clause]'='simple-type-spec';'
Member list:
member-list=
(var-decl
|type-decl
|function-decl
|constructor-decl
|property-decl
|subscript-op-decl
|function-call-op-decl)*
Parameters
modifier-listis an optional list of modifiers (TODO: link)identifieris an optional name of the declared struct typegeneric-params-declis an optional generic parameters declaration. See generics.bases-clauseis an optional list of inherited interfaces.simple-type-specis an optional type expression for an alias type. See Modules (TODO).where-clauseis an optional generic constraint expression. See generics.member-listis a list of struct members. A member is one of:var-declis a member variable declaration. See Variables (TODO)type-declis a nested type declaration.function-declis a member function declaration. See Functions (TODO)constructor-declis a constructor declaration.property-declis a property declaration.subscript-op-declis a subscript operator declaration.function-call-op-declis a function call operator declaration.
⚠️ Warning: The syntax for bracketed attributes list after
'struct'was deprecated in Slang 2025, and it has been removed in Slang 2026. Add bracketed attributes inmodifier-listbefore the'struct'keyword, instead. (Issue #9691)
Description
A structure is a type consisting of an ordered sequence of members. A struct declaration has the following
forms:
- The no-body declaration specifies an existence of a structure type. The declaration simply specifies that a structure type with the specified name exists. This enables its use in type expressions without the member declarations.
- The with-members declaration defines the structure type with a layout and an extensible list of non-layout members.
- The link-time extern type declaration specifies the existence of a structure type that is defined in another module. See Modules (TODO).
- The link-time export type declaration specifies that a structure type is exported with a type alias. See Modules (TODO).
When the identifier is specified, it is the name of the structure. Otherwise, the structure is anonymous,
which means that it is assigned an unspecified unique name. The main use of anonymous structures is in inline
type definition expressions. For example, struct { int a; } obj; defines variable obj with an anonymous
structure type that has field int a;. Anonymous structure declarations are meaningful only in the
with-members form.
A structure member is declared in the structure body and is one of the following:
- A static data member; declared as a variable with the
statickeyword. - A non-static data member (aka. field); declared as a variable.
- A constructor.
- A static member function; declared as a function with the
staticmodifier keyword. - A non-static member function; declared as a function without the
staticmodifier keyword. - A nested type; declared as a type or a type alias.
- A
propertydeclaration. - A
__subscriptdeclaration. - A function call operator declaration.
A data member and a member function can be declared with the static keyword.
- The storage for a static data member is allocated from the global storage. A static member function may:
- Access static data members of the structure.
- Invoke other static member functions of the structure.
- The storage for a non-static data member is allocated as part of the structure. A non-static member function may:
- Access both the static and the non-static data members.
- Invoke both the static and the non-static member functions.
Data members may be assigned with a default initializer. The following rules apply:
- When an object is initialized using an initializer list, the default initializer of a non-static data member specifies the initial value when the initializer list does not provide one.
- When an object is initialized using a constructor, the default initializer of a non-static data member
specifies the initial value of the data member. A constructor may override this, unless the member is
const. static constdata members must have a default initializer.
For further information, see Initialization (TODO).
The non-static data members are allocated sequentially within the struct when a variable of this type is
allocated. See Variables (TODO).
A nested type is a regular type enclosed within the scope of the outer struct.
A structure may conform to one or more interface types.
A structure may be extended with a type extension.
struct members may be declared with access control specifiers public, internal, or private (specified
in modifier-list). The default member visibility is internal, except that starting with language version
2026 a member with no explicit specifier inherits the visibility of its enclosing struct (so an unmodified
member of a public struct is public). Nested struct members have access to
private members of the enclosing struct. See access control (TODO) for further information.
⚠️ Warning: Struct-from-struct inheritance is unstable in Slang 2025 and earlier language versions, and has been removed in Slang 2026. Use composition (a struct as a member) instead.
Objects
An object is an instance of a struct. An instance consists of all non-static data members defined in a
struct. The data members may be initialized using an initializer list or a constructor. For details, see
variable declarations.
Constructors
Syntax
Declaration without body: (interfaces only)
'__init'[generic-params-decl]'('param-list')'('where'where-clause)*';'
Declaration with body:
'__init'[generic-params-decl]'('param-list')'('where'where-clause)*
'{'body-stmt*'}'
Description
When a user-provided constructor is defined for a struct, a constructor is executed on object
instantiation. A constructor can have any number of parameters. A constructor does not have a return
type. More than one constructors may be defined in which case overload resolution is performed to select the
most appropriate constructor given the initialization parameters.
The constructor parameters are provided in the optional initializer list. When an initializer is not provided, the no-parameter constructor is invoked.
If a non-static data member is not initialized by the constructor, it has an undefined state after object instantiation.
const data members cannot be initialized by the constructor.
where-clause is an optional generic constraint expression, discussed in generics.
Example:
struct TestClass
{
int a, b;
__init()
{
a = 1;
b = 2;
}
__init(int _a)
{
a = 1;
b = 2;
}
__init(int _a, int _b)
{
a = _a;
b = _b;
}
}
TestClass obj1;
// obj1.a = 1;
// obj1.b = 2;
//
// Note: TestClass obj1 = { }; also calls the constructor
// without parameters
TestClass obj2 = { 42 };
// obj2.a = 42;
// obj2.b = 2;
TestClass obj3 = { 42, 43 };
// obj3.a = 42;
// obj3.b = 43;
When no user-provided constructor is defined, an aggregate initialization is performed, instead. In aggregate
initialization, an initializer list contains values for the struct non-static data members. If the
initializer list does not contain enough values, the remaining data members are default-initialized. If no
initializer list is provided, a class without a user-provided constructor is instantiated in an undefined
state.
📝 Remark 1: When a class without user-provided constructor is instantiated without an initializer list, the object’s initial state is undefined. This includes data members which have members with user-provided constructors.
struct TestField { int x; __init() { x = 5; } } struct TestClass { int a, b; TestField f; } // note: obj is instantiated with an undefined state // regardless of TestField having a user-provided constructor. TestClass obj;
📝 Remark 2: Accessing data members that are in undefined state is undefined behavior.
Static Member Functions
A static member function is a regular function enclosed within the struct name space. Static member
functions may access only static structure members.
Invocation of a static member function does not require an object.
Non-static Member Functions
A non-static member function has a hidden parameter this that refers to an object. The hidden parameter
is used to reference the object data members and to invoke other non-static member functions.
In the function body, other members may be referenced using this., although it is optional.
By default, only a read access to the object members is allowed by a member function. If write access is
required, the member function must be declared with the [mutating] attribute.
Non-static member functions cannot be invoked without an object.
📝 Remark: In C++ terminology, a member function is
constby default. Attribute[mutating]makes it a non-constmember function.
Properties
Syntax
Modern syntax, implicit get declaration: (interfaces only)
'property'identifier':'simple-type-spec';'
Modern syntax, explicit accessor declarations:
'property'identifier':'simple-type-spec
'{'accessor-decl*'}'
Traditional syntax, implicit get declaration: (interfaces only)
'property'traditional-var-decl';'
Traditional syntax, explicit accessor declarations:
'property'traditional-var-decl
'{'accessor-decl*'}'
Accessor declaration syntax, no body: (interfaces only)
accessor-decl=
('get'|'set') ['('param-list')']';'
Accessor declaration syntax, with body:
accessor-decl=
('get'|'set') ['('param-list')']
'{'body-stmt*'}'
Description
A property is a non-static member that provides a data member access interface. Properties of objects are
accessed similarly to data members: reading a property is directed to the get accessor of the property and
writes are directed to the set accessor, respectively.
A property that only provides the get accessor is a read-only property. A property that only provides the
set accessor is a write-only property. A property that provides both is a read/write property.
The parentheses in the get accessor declaration are optional. The get accessor accepts no parameters.
The parentheses and the parameter in the set accessor declaration are optional. In case the parameter is not
specified in the declaration, parameter newValue with the same type as the property is provided to the set
body.
The property declaration forms without accessor or accessor body declarations are useful only in interface declarations.
⚠️ Warning: Property reference accessor
refis a Slang internal language feature. It is subject to change and may not work as expected.
Example:
struct TestClass
{
float m_val;
// automatically updated derivative of m_val
bool m_valIsPositive;
property someProp : float
{
get
{
return m_val;
}
set
{
m_val = newValue;
m_valIsPositive = (newValue > 0.0f);
}
}
}
[shader("compute")]
void main(uint3 id : SV_DispatchThreadID)
{
TestClass obj = { };
// this sets both obj.m_val and obj.m_valIsPositive
obj.someProp = 3.0f;
}
📝 Remark 1: A property can be used to replace a non-
staticdata member when additional logic is desired to be added systematically to data member access. This can avoid refactoring call sites.
📝 Remark 2: A non-static data member can be used to implement an interface property requirement. See interfaces for details.
📝 Remark 3: In the example above, the property could have also been declared as:
struct TestClass { // ... property someProp : float { get() { return m_val; } set(float newVal) { m_val = newVal; m_valIsPositive = (newVal > 0.0f); } } }
Accessing Members and Nested Types
The static and non-static structure members and nested types are accessed using `.`.
⚠️ Warning: The C++-style scope resolution operator `
::` is deprecated. It should not be used.
Example:
// struct type declaration
struct TestStruct
{
// data member
int a;
// static data member, initial value 5
static int b = 5;
// static constant data member, initial value 6
static const int c = 6;
// nested type
struct NestedStruct
{
static int c = 6;
int d;
}
// member function with read-only access
// to non-static data members
int getA()
{
// also just plain "return a" would do
return this.a;
}
// member function with read/write access
// to non-static data members
[mutating] int incrementAndReturnA()
{
// modification of data member
// requires [mutating]
a = a + 1;
return a;
}
// static member function
static int getB()
{
return b;
}
static int incrementAndReturnB()
{
// [mutating] not needed for
// modifying static data member
b = b + 1;
return b;
}
}
// instantiate an object of type TestStruct using defaults
TestStruct obj = { };
// instantiate an object of type NestedStruct
TestStruct.NestedStruct obj2 = { };
// access an object data member directly
obj.a = 42;
// access a static data member directly
int tmp0 = TestStruct.b + TestStruct.NestedStruct.c;
// invoke object member functions
int tmp1 = obj.getA();
int tmp2 = obj.incrementAndReturnA();
// invoke static members functions
// '.' can be used to resolve scope
int tmp3 = TestStruct.getB();
// '::' is equivalent to '.' for static member access,
// but '.' is recommended.
int tmp4 = TestStruct::incrementAndReturnB();
Subscript operator
Syntax
Implicit get declaration: (interfaces only)
'__subscript''('param-list')''->'simple-type-spec';'
Explicit accessor declarations:
'__subscript''('param-list')''->'simple-type-spec
'{'accessor-decl*'}'
See properties for accessor-decl syntax.
Description
A subscript [] operator can be added in a structure using a __subscript declaration. It is conceptually
similar to a property with the main differences being that it operates on the instance of a struct
(instead of a member) and it accepts parameters.
A subscript declaration may have any number of parameters, including no parameters at all.
The get accessor of a __subscript declaration is invoked when the subscript operator is applied to an
object to return a value. The parentheses in the get accessor declaration are optional.
The set accessor of a __subscript declaration is invoked when the subscript operator is applied to an
object to assign a value. The parentheses and the parameter in the set accessor declaration are optional. In
case the parameter is not specified in the declaration, a parameter newValue with the same type as specified
for the subscript operator is provided to the set body.
Multiple __subscript declarations are allowed as long as the declarations have different
signatures. Overload resolution is the same as overload resolution with function invocations.
⚠️ Warning: Subscript operator reference accessor
refis a Slang internal language feature. It is subject to change and may not work as expected.
Example:
RWStructuredBuffer<float> outputBuffer;
struct TestStruct
{
var arr : float[10][10];
// declare a 0-parameter subscript operator
__subscript () -> float
{
get { return arr[0][0]; }
set { arr[0][0] = newValue; }
}
// declare a 1-parameter subscript operator
__subscript (int i) -> float
{
get { return arr[0][i]; }
set { arr[0][i] = newValue; }
}
// declare a 2-paramater subscript operator
__subscript (int i0, int i1) -> float
{
get { return arr[i1][i0]; }
set { arr[i1][i0] = newValue; }
}
}
[numthreads(1,1,1)]
void main(uint3 id : SV_DispatchThreadID)
{
TestStruct x = { };
x[] = id.z;
x[id.y] = id.z;
x[id.x, id.y] = id.z;
outputBuffer[id.x] = x[];
outputBuffer[id.y] = x[id.x];
outputBuffer[id.z] = x[id.x, id.y];
}
Function call operator
Syntax
Declaration without body: (interfaces only)
simple-type-spec'operator''(' ')''('param-list')'';'
Declaration with body:
simple-type-spec'operator''(' ')''('param-list')'
'{'body-stmt*'}'
Description
A function call () operator can be added using an operator () declaration. This allows applying parameters
to an object as if the object was a function.
Multiple declarations are allowed as long as the declarations have different signatures. Overload resolution is the same as overload resolution with function invocations.
Example:
RWStructuredBuffer<float> outputBuffer;
struct TestStruct
{
float base;
float operator () ()
{
return base;
}
float operator () (uint x)
{
return base * float(x);
}
float operator () (uint x, uint y)
{
return base * float(x) * float(y);
}
}
void main(uint3 id : SV_DispatchThreadID)
{
TestStruct obj = { 42.0f };
outputBuffer[0] += obj();
outputBuffer[0] += obj(id.y);
outputBuffer[0] += obj(id.z, id.z * 2);
}
Memory Layout
Natural Layout
The natural layout for a structure type uses the following rules:
- The alignment of a structure is the maximum of 1, alignment of any member, and alignment of any parent type.
- The data is laid out in order of:
- Parent types
- Non-static data members
- Offset of the data items:
- The offset of the first data item is 0
- The offset of the Nth data item is the offset+size of the previous item rounded up to the alignment of the item
- The size of the structure is offset+size of the last item. That is, the structure is not tail-padded and rounded up to the alignment of the structure.
The following algorithm may be used:
- Initialize variables
sizeandalignmentto zero and one, respectively - For each field
fof the structure type:- Update
alignmentto be the maximum ofalignmentand the alignment off - Set
sizeto the smallest multiple ofalignmentnot less thansize - Set the offset of field
ftosize - Add the size of
ftosize
- Update
When this algorithm completes, size and alignment will be the size and alignment of the structure type.
📝 Remark: Most target platforms do not use the natural layout directly, but it provides a baseline for defining other layouts. Any layout for a structure type must guarantee an alignment at least as large as the standard layout.
C-Style Layout
The C-style layout of a structure type differs from the natural layout in that the structure size is rounded up to the structure alignment. This mirrors the layout rules used by typical C/C++ compilers.
D3D Constant Buffer Layout
D3D constant buffer layout is similar to the natural layout with two differences:
- The minimum alignment is 16.
- If a data member crosses a 16-byte boundary and its offset is not aligned by 16, the offset is rounded up to the
next multiple of 16.
- In HLSL, this is called an improper straddle.
This Type
Within the body of a structure or interface declaration, the keyword This may be used to refer to the
enclosing type. Inside of a structure type declaration, This refers to the structure type itself. Inside
of an interface declaration, This refers to the concrete type that is conforming to the interface (that is,
the type of this).