Functions - D Programming Language (original) (raw)
Contents
- Function Declarations
- Function Contracts
- Function Return Values
- Pure Functions
- Nothrow Functions
- Ref Functions
- Auto Functions
- Auto Ref Functions
- Inout Functions
- Optional Parentheses
- Property Functions
- Virtual Functions
- Covariance
- Calling Base Class Methods
- Overload Sets and Overriding
- Default Values
- Inherited Attributes
- Restrictions
- Inline Functions
- Function Overloading
- Overload Sets
- Function Parameters
- Parameter Storage Classes
- In Parameters
- Ref and Out Parameters
- Lazy Parameters
- Default Arguments
- Return Ref Parameters
- Scope Parameters
- Return Scope Parameters
- Ref Return Scope Parameters
- Inferred scope parameters in pure functions
- User-Defined Attributes for Parameters
- Variadic Functions
- Hidden Parameters
- Ref Scope Return Cases
- Definitions
- Classification
- Mapping Syntax Onto Classification
- Member Functions
- P and ref
- Covariance
- Local Variables
- Local Static Variables
- Nested Functions
- Declaration Order
- Function Pointers, Delegates and Closures
- Function Pointers
- Delegates & Closures
- Initialization
- Anonymous Functions and Anonymous Delegates
- main() Function
- extern(C) main() Function
- Function Templates
- Compile Time Function Execution (CTFE)
- String Mixins and Compile Time Function Execution
- No-GC Functions
- Function Safety
- Safe Functions
- Trusted Functions
- System Functions
- Safe Interfaces
- Safe Values
- Safe Aliasing
- Function Attribute Inference
- Uniform Function Call Syntax (UFCS)
Function Declarations
FuncDeclaration: StorageClassesopt BasicType FuncDeclarator FunctionBody StorageClassesopt BasicType FuncDeclarator MissingFunctionBody AutoFuncDeclaration
AutoFuncDeclaration: StorageClasses Identifier FuncDeclaratorSuffix FunctionBody
FuncDeclarator: TypeSuffixesopt Identifier FuncDeclaratorSuffix
FuncDeclaratorSuffix: Parameters MemberFunctionAttributesopt TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt
- A FuncDeclaration with a function body is called afunction definition.
- A FuncDeclaration without a function body is called afunction prototype.
- A FuncDeclaration with TemplateParameters defines afunction template.
Function Parameters
Parameters: ( ParameterListopt )
ParameterList: Parameter Parameter , _ParameterList_opt VariadicArgumentsAttributesopt ...
Parameter: ParameterDeclaration ParameterDeclaration ... ParameterDeclaration = AssignExpression ParameterDeclaration = AssignExpression ...
ParameterDeclaration: ParameterAttributesopt BasicType Declarator ParameterAttributesopt Type
ParameterAttributes: ParameterStorageClass UserDefinedAttribute ParameterAttributes ParameterStorageClass ParameterAttributes UserDefinedAttribute
ParameterStorageClass: auto TypeCtor final in lazy out ref return scope
VariadicArgumentsAttributes: VariadicArgumentsAttribute VariadicArgumentsAttribute VariadicArgumentsAttributes
VariadicArgumentsAttribute: const immutable return scope shared
Note: In D2, declaring a parameter final is a semantic error, but not a parse error.
See also: parameter storage classes.
Function Attributes
FunctionAttributes: FunctionAttribute FunctionAttribute FunctionAttributes
FunctionAttribute: FunctionAttributeKwd Property AtAttribute
MemberFunctionAttributes: MemberFunctionAttribute MemberFunctionAttribute MemberFunctionAttributes
MemberFunctionAttribute: const immutable inout return scope shared FunctionAttribute
Function Bodies
FunctionBody: SpecifiedFunctionBody ShortenedFunctionBody
SpecifiedFunctionBody: doopt BlockStatement FunctionContractsopt InOutContractExpression doopt BlockStatement FunctionContractsopt InOutStatement do BlockStatement
ShortenedFunctionBody: InOutContractExpressionsopt => AssignExpression ;
Examples:
int hasSpecifiedBody() { return 1; } int hasShortenedBody() => 1;
The ShortenedFunctionBody form implies areturn statement. This syntax also applies for function literals.
Function Prototypes
MissingFunctionBody: ; FunctionContractsopt InOutContractExpression ; FunctionContractsopt InOutStatement
Function declarations with a MissingFunctionBody, e.g.:
int foo();
that are not declared as abstract are expected to have their implementations elsewhere, and that implementation will be provided at the link step. This enables an implementation of a function to be completely hidden from the user of it, and the implementation may be in another language such as C, assembler, etc. Typically a function prototype would have non-extern(D) linkage.
A function prototype can have extern(D) linkage. This is useful for D interface files.
Function Contracts
FunctionContracts: FunctionContract FunctionContract FunctionContracts
FunctionContract: InOutContractExpression InOutStatement
InOutContractExpressions: InOutContractExpression InOutContractExpression InOutContractExpressions
InOutContractExpression: InContractExpression OutContractExpression
InOutStatement: InStatement OutStatement
Function Contracts specify the preconditions and postconditions of a function. They are used in Contract Programming.
Preconditions and postconditions do not affect the type of the function.
Preconditions
InContractExpression: in ( AssertArguments )
InStatement: in BlockStatement
An InContractExpression is a precondition.
- The first AssignExpression of the AssertArguments must evaluate to true. If it does not, the precondition has failed.
- The second AssignExpression, if present, must be implicitly convertible to type const(char)[].
An InStatement is also a precondition. Any AssertExpression appearing in an InStatement will be an InContractExpression.
Preconditions must semantically be satisfied before the function starts executing. If a precondition fails, the program enters an Invalid State.
Implementation Defined: Whether the preconditions are actually run or not is implementation defined. This is usually selectable with a compiler switch. Its behavior upon precondition failure is also usually selectable with a compiler switch. One option is to throw an AssertError with a message consisting of the optional second_AssignExpression_.
Best Practices: Use preconditions to validate that input arguments have values that are expected by the function.
Best Practices: Since preconditions may or may not be actually checked at runtime, avoid using preconditions that have side effects.
The expression form is:
in (expression) in (expression, "failure string") { ...function body... }
The block statement form is:
in { ...contract preconditions... } do { ...function body... }
Postconditions
OutContractExpression: out ( ; AssertArguments ) out ( Identifier ; AssertArguments )
OutStatement: out BlockStatement out ( Identifier ) BlockStatement
An OutContractExpression is a postcondition.
- The first AssignExpression of the AssertArguments must evaluate to true. If it does not, the postcondition has failed.
- The second AssignExpression, if present, must be implicitly convertible to type const(char)[].
An OutStatement is also a postcondition. Any AssertExpression appearing in an OutStatement will be an OutContractExpression.
Postconditions must semantically be satisfied after the function finishes executing. If a postcondition fails, the program enters an Invalid State.
Implementation Defined: Whether the postconditions are actually run or not is implementation defined. This is usually selectable with a compiler switch. Its behavior upon postcondition failure is also usually selectable with a compiler switch. One option is to throw an AssertError with a message consisting of the optional second_AssignExpression_.
Best Practices: Use postconditions to validate that on leaving the function:
- Any mutable input arguments each have the expected value/range of values.
- Any return value has a correct value/range of values.
Best Practices: Since postconditions may or may not be actually checked at runtime, avoid using postconditions that have side effects.
The expression form is:
out (identifier; expression) out (identifier; expression, "failure string") out (; expression) out (; expression, "failure string") { ...function body... }
The block statement form is:
out { ...contract postconditions... } out (identifier) { ...contract postconditions... } do { ...function body... }
The optional identifier in either type of postcondition is set to the return value of the function, and can be accessed from within the postcondition. It is implicitly const.
Example
int fun(ref int a, int b) in (a > 0) in (b >= 0, "b cannot be negative!") out (r; r > 0, "return must be positive") out (; a != 0) { }
int fun(ref int a, int b) in { assert(a > 0); assert(b >= 0, "b cannot be negative!"); } out (r) { assert(r > 0, "return must be positive"); assert(a != 0); } do { }
The two functions are identical semantically.
In, Out and Inheritance
If a function in a derived class overrides a function from its super class, then only the preconditions of one of the function and its overridden functions must be satisfied. Overriding functions then becomes a process of loosening the preconditions.
A function without preconditions means its precondition is always satisfied. Therefore if any function in an inheritance hierarchy has no preconditions, then any preconditions on functions overriding it have no meaningful effect.
Conversely, all of the postconditions of the function and its overridden functions must to be satisfied. Adding overriding functions then becomes a processes of tightening the postconditions.
Function Return Values
At least one return statement is required if the function specifies a return type that is not void, unless:
- the function executes an infinite loop
- the function executes an assert(0) statement
- the function evaluates an expression of typenoreturn
- the function contains inline assembler code
Function return values not marked as ref are considered to be rvalues. This means they cannot be passed by reference to other functions.
Pure Functions
Pure functions are annotated with the pure attribute. Pure functions cannot directly access global or static mutable state. Pure functions can only call pure functions.
Pure functions can:
- Modify the local state of the function.
- Throw exceptions.
int x; immutable int y;
pure int foo(int i) { i++; i = y; throw new Exception("failed"); }
A pure function can override an impure function, but cannot be overridden by an impure function. I.e. it is covariant with an impure function.
Strong vs Weak Purity
A weakly pure function has parameters with mutable indirections. Program state can be modified transitively through the matching argument.
pure size_t foo(int[] arr) { arr[] += 1; return arr.length; } int[] a = [1, 2, 3]; foo(a); assert(a == [2, 3, 4]);
A strongly pure function has no parameters with mutable indirections and cannot modify any program state external to the function.
struct S { double x; }
pure size_t foo(immutable(int)[] arr, int num, S val) { num = 2; val.x = 3.14; return arr.length; }
A strongly pure function can call a weakly pure function.
Special Cases
A pure function can:
- read and write the floating point exception flags
- read and write the floating point mode flags, as long as those flags are restored to their initial state upon function entry
Undefined Behavior: occurs if these flags are not restored to their initial state upon function exit. It is the programmer's responsibility to ensure this. Setting these flags is not allowed in @safe code.
Debugging
A pure function can perform impure operations in statements that are in aConditionalStatement controlled by a DebugCondition.
Best Practices: this relaxation of purity checks in _DebugCondition_s is intended solely to make debugging programs easier.
pure int foo(int i) { debug writeln("i = ", i); ... }
Nested Functions
Nested functions inside a pure function are implicitly marked as pure.
pure int foo(int x, immutable int y)
{
int bar()
{
x = 10; return x;
}
pragma(msg, typeof(&bar));
int baz() immutable
{
return y; }
return bar() + baz();
}
Pure Factory Functions
A pure factory function is a strongly pure function that returns a result that has only mutable indirections. All mutable memory returned by the call cannot be referenced by any other part of the program, i.e. it is newly allocated by the function. The mutable references of the result similarly cannot refer to any object that existed before the function call. This allows the result to be implicitly cast from anything to immutable or const shared, and from shared and const shared to (unshared) const. For example:
struct List { int payload; List* next; }
pure List* make(int a, int b) { auto result = new List(a, null); result.next = new List(b, null); return result; }
void main()
{
auto list = make(1, 2);
pragma(msg, typeof(list));
immutable ilist = make(1, 2);
pragma(msg, typeof(ilist)); pragma(msg, typeof(ilist.next)); }
All references in make's result refer to List objects created by make, and no other part of the program refers to any of these objects. Hence the result can initialize an immutable variable.
This does not affect any Exception or Error thrown from the function.
Optimization
Implementation Defined: An implementation may assume that a strongly pure function called with arguments that have only immutable indirections (or none) that returns a result without mutable indirections will have the same effect for all invocations with equivalent arguments. It is allowed to memoize the result of the function under the assumption that equivalent arguments always produce equivalent results.
int a(int) pure; int b(const Object) pure; immutable(Object) c(immutable Object) pure; void g();
void f(int n, const Object co, immutable Object io) { const int x = a(n); g(); int i = a(n); const int y = b(co); g(); i = b(co); const int z = b(io); i = b(io); }
Such a function may still have behavior inconsistent with memoization by e.g. using casts or by changing behavior depending on the address of its parameters. An implementation is currently not required to enforce validity of memoization in all cases.
If a function throws an Exception or an Error, the assumptions related to memoization do not carry to the thrown exception.
Pure destructors do not benefit of special elision.
Nothrow Functions
Nothrow functions can only throw exceptions derived from class Error.
Nothrow functions are covariant with throwing ones.
Ref Functions
A ref function returns by reference (instead of by value). The return value of a ref function must be an lvalue (whereas the return value of a non-ref function can be an rvalue, too). An expression formed by calling a ref function is an lvalue (whereas an expression formed by calling a non-ref function is an rvalue).
int *p;
ref int foo() { p = new int(2); return *p; }
void main() { int i = foo(); assert(i == 2);
foo() = 3; assert(*p == 3);
}
Returning a reference to an expired function context is not allowed. This includes local variables, temporaries and parameters that are part of an expired function context.
ref int sun() { int i; return i; }
A ref parameter may not be returned by ref, unless it isreturn ref.
ref int moon(ref int i) { return i; }
Auto Functions
Auto functions have their return type inferred from anyReturnStatements in the function body.
An auto function is declared without a return type. Auto functions can use any valid StorageClass, not just auto.
If there are multiple _ReturnStatement_s, the types of them must be implicitly convertible to a common type. If there are no _ReturnStatement_s, the return type is inferred to be void.
auto foo(int x) { return x + 3; } pure bar(int x) { return x; return 2.5; }
Note: Return type inference also triggersattribute inference.
Auto Ref Functions
Auto ref functions can infer their return type just asauto functions do. In addition, they become ref functions if all of these apply:
- All expressions returned from the function are lvalues
- No local variables are returned
- Any parameters returned are reference parameters
- Each returned expression must implicitly convert to an lvalue of the deduced return type
auto ref f1(int x) { return x; } auto ref f2() { return 3; } auto ref f3(ref int x) { return x; } auto ref f4(out int x) { return x; } auto ref f5()
{
static int x;
return x; }
The ref-ness of a function is determined from allReturnStatements in the function body:
auto ref f1(ref int x) { return 3; return x; } auto ref f2(ref int x) { return x; return 3; } auto ref f3(ref int x, ref double y) { return x; return y; }
Auto ref functions can have an explicit return type.
auto ref int bar(ref int x) { return x; } auto ref int foo(double x) { return x; }
Inout Functions
For extensive information see inout type qualifier.
Optional Parentheses
If a function call passes no explicit argument, i.e. it would syntactically use (), then these parentheses may be omitted, similar to a getter invocation of aproperty function. A UFCS call can also omit empty parentheses.
void foo() {} void fun(int x = 10) {} void bar(int[] arr) {}
void main()
{
foo(); foo; fun;
int[] arr;
arr.bar(); arr.bar; }
Due to ambiguity, parentheses are required to call a delegate or a function pointer:
void main() { int function() fp;
assert(fp == 6); assert(*fp == 6);
int delegate() dg;
assert(dg == 6); }
If a function returns a delegate or a function pointer, any parentheses apply first to the function call, not the result. Two sets of parentheses are required to call the result directly:
int getNum() { return 6; } int function() getFunc() { return &getNum; }
void main() { int function() fp;
fp = getFunc; assert(fp() == 6);
fp = getFunc(); assert(fp() == 6);
int x = getFunc()();
assert(x == 6);
}
struct S { int getNum() { return 6; } int delegate() getDel() return { return &getNum; } }
void main() { S s; int delegate() dg;
dg = s.getDel; assert(dg() == 6);
dg = s.getDel(); assert(dg() == 6);
int y = s.getDel()();
assert(y == 6);
}
Property Functions
WARNING: The definition and usefulness of property functions is being reviewed, and the implementation is currently incomplete. Using property functions is not recommended until the definition is more certain and implementation more mature.
Properties are functions that can be syntactically treated as if they were fields or variables. Properties can be read from or written to. A property is read by calling a method or function with no arguments; a property is written by calling a method or function with its argument being the value it is set to.
Simple getter and setter properties can be written using UFCS. These can be enhanced with the additon of the @property attribute to the function, which adds the following behaviors:
- @property functions cannot be overloaded with non-@property functions with the same name.
- @property functions can only have zero, one or two parameters.
- @property functions cannot have variadic parameters.
- For the expression typeof(exp) where exp is an @property function, the type is the return type of the function, rather than the type of the function.
- For the expression __traits(compiles, exp) where exp is an @property function, a further check is made to see if the function can be called.
- @property are mangled differently, meaning that @property must be consistently used across different compilation units.
- The ObjectiveC interface recognizes @property setter functions as special and modifies them accordingly.
A simple property would be:
struct Foo { @property int data() { return m_data; } @property int data(int value) { return m_data = value; } private: int m_data; }
To use it:
int test() { Foo f;
f.data = 3; return f.data + 3; }
The absence of a read method means that the property is write-only. The absence of a write method means that the property is read-only. Multiple write methods can exist; the correct one is selected using the usual function overloading rules.
In all the other respects, these methods are like any other methods. They can be static, have different linkages, have their address taken, etc.
The built in properties .sizeof, .alignof, and .mangleof may not be declared as fields or methods in structs, unions, classes or enums.
If a property function has no parameters, it works as a getter. If has exactly one parameter, it works as a setter.
Virtual Functions
Virtual functions are class member functions that are called indirectly through a function pointer table, called a vtbl[], rather than directly. Member functions that are virtual can be overridden in a derived class:
class A { void foo(int x) {} }
class B : A { override void foo(int x) {} }
void test() { A a = new B(); a.foo(1); }
The override attribute is required when overriding a function. This is useful for catching errors when a base class's member function has its parameters changed, and all derived classes need to have their overriding functions updated.
The final method attribute prevents a subclass from overriding the method.
The following are not virtual:
- Struct and union member functions
- final member functions
- static member functions
- Member functions which are private or package
- Member template functions
Example:
class A { int def() { ... } final int foo() { ... } final private int bar() { ... } private int abc() { ... } }
class B : A { override int def() { ... } override int foo() { ... } int bar() { ... } int abc() { ... } }
void test() { A a = new B; a.def(); a.foo(); a.bar(); a.abc(); }
Member functions with Objective-C linkage are virtual even if marked with final or static, and can be overridden.
Covariance
An overriding function may be covariant with the overridden function. A covariant function has a type that is implicitly convertible to the type of the overridden function.
class A { } class B : A { }
class Foo { A test() { return null; } }
class Bar : Foo { override B test() { return null; } }
Calling Base Class Methods
To directly call a member function of a base class Base, write Base. before the function name. This avoids dynamic dispatch through a function pointer. For example:
class B { int foo() { return 1; } } class C : B { override int foo() { return 2; }
void test()
{
assert(B.foo() == 1); assert(C.foo() == 2); }
} class D : C { override int foo() { return 3; } } void main() { auto d = new D(); assert(d.foo() == 3); assert(d.B.foo() == 1); assert(d.C.foo() == 2); d.test(); }
Base class methods can also be called through thesuper reference.
Implementation Defined: Normally calling a virtual function implies getting the address of the function at runtime by indexing into the class's vtbl[]. If the implementation can determine that the called virtual function will be statically known, such as if it is final, it can use a direct call instead.
Overload Sets and Overriding
When doing overload resolution, the functions in the base class are not considered, as they are not in the sameOverload Set:
class A { int foo(int x) { ... } int foo(long y) { ... } }
class B : A { override int foo(long x) { ... } }
void test()
{
B b = new B();
b.foo(1);
A a = b;
a.foo(1); }
To include the base class's functions in the overload resolution process, use an AliasDeclaration:
class A { int foo(int x) { ... } int foo(long y) { ... } }
class B : A { alias foo = A.foo; override int foo(long x) { ... } }
void test() { A a = new B(); a.foo(1); B b = new B(); b.foo(1); }
If such an AliasDeclaration is not used, the derived class's functions completely override all the functions of the same name in the base class, even if the types of the parameters in the base class functions are different. It is illegal if, through implicit conversions to the base class, those other functions do get called:
class A { void set(long i) { } void set(int i) { } } class B : A { override void set(long i) { } }
void test() { A a = new B; a.set(3); }
Default Values
A function parameter's default value is not inherited:
class A { void foo(int x = 5) { ... } }
class B : A { void foo(int x = 7) { ... } }
class C : B { void foo(int x) { ... } }
void test()
{
A a = new A();
a.foo();
B b = new B();
b.foo();
C c = new C();
c.foo(); }
Inherited Attributes
An overriding function inherits any unspecified FunctionAttributes from the attributes of the overridden function.
class B { void foo() pure nothrow @safe {} } class D : B { override void foo() {} } void main() { auto d = new D(); pragma(msg, typeof(&d.foo)); }
Restrictions
The attributes@disable anddeprecated are not allowed on overriding functions.
Rationale: To stop the compilation or to output the deprecation message, the implementation must be able to determine the target of the call, which can't be guaranteed when it is virtual.
class B { void foo() {} }
class D : B { @disable override void foo() {} }
Inline Functions
The compiler makes the decision whether to inline a function or not. This decision may be controlled by pragma(inline).
Implementation Defined: Whether a function is inlined or not is implementation defined, though any FunctionLiteral should be inlined when used in its declaration scope.
Function Overloading
Function overloading occurs when two or more functions in the same scope have the same name. The function selected is the one that is the best match to the arguments. The matching levels are:
- No match
- Match with implicit conversions
- Match with qualifier conversion (if the argument type isqualifier-convertible to the parameter type)
- Exact match
Named arguments are resolved for a candidate according toMatching Arguments to Parameters. If this fails (for example, because the overload does not have a parameter matching a named argument), the level is no match. Other than that, named arguments do not affect the matching level.
Each argument (including any this reference) is compared against the function's corresponding parameter to determine the match level for that argument. The match level for a function is the worst match level of each of its arguments.
Literals do not match ref or out parameters.
scope parameter storage class does not affect function overloading.
If two or more functions have the same match level, then partial ordering is used to disambiguate to find the best match. Partial ordering finds the most specialized function. If neither function is more specialized than the other, then it is an ambiguity error. Partial ordering is determined for functions f and g by taking the parameter types of f, constructing a list of arguments by taking the default values of those types, and attempting to match them against g. If it succeeds, then g is at least as specialized as f. For example:
class A { } class B : A { } class C : B { } void foo(A); void foo(B);
void test() { C c;
foo(c); }
A function with a variadic argument is considered less specialized than a function without.
A static member function can be overloaded with a member function. The struct, class or union of the static member function is inferred from the type of the this argument.
struct S { void eggs(int); static void eggs(long); } S s; s.eggs(0); S.eggs(0); s.eggs(0L); S.eggs(0L); struct T { void bacon(int); static void bacon(int); } T t; t.bacon(0); T.bacon(0);
Rationale: A static member function that doesn't need the this parameter does not need to pass it.
Overload Sets
Functions declared at the same scope overload against each other, and are called an Overload Set. An example of an overload set are functions defined at module level:
module A; void foo() { } void foo(long i) { }
A.foo() and A.foo(long) form an overload set. A different module can also define another overload set of functions with the same name:
module B; class C { } void foo(C) { } void foo(int i) { }
and A and B can be imported by a third module, C. Both overload sets, the A.foo overload set and the B.foo overload set, are found when searching for symbol foo. An instance of foo is selected based on it matching in exactly one overload set:
import A; import B;
void bar(C c , long i) { foo(); foo(i); foo(c); foo(1,2); foo(1); A.foo(1); }
Even though B.foo(int) is a better match than A.foo(long) for foo(1), it is an error because the two matches are in different overload sets.
Overload sets can be merged with an alias declaration:
import A; import B;
alias foo = A.foo; alias foo = B.foo;
void bar(C c) { foo(); foo(1L); foo(c); foo(1,2); foo(1); A.foo(1); }
Function Parameters
Parameter Storage Classes
Parameter storage classes are in, out, ref, lazy, return and scope. Parameters can also take the type constructors const, immutable, shared and inout.
in, out, ref and lazy are mutually exclusive. The first three are used to denote input, output and input/output parameters, respectively. For example:
int read(in char[] input, ref size_t count, out int errno);
void main() { size_t a = 42; int b; int r = read("Hello World", a, b); }
read has three parameters. input will only be read and no reference to it will be retained.count may be read and written to, and errno will be set to a value from within the function.
The argument "Hello World" gets bound to parameter input,a gets bound to count and b to errno.
Parameter Storage Class and Type Constructor Overview
Storage Class | Description |
---|---|
none | The parameter will be a mutable copy of its argument. |
in | The parameter is an input to the function. |
out | The argument must be an lvalue, which will be passed by reference and initialized upon function entry with the default value (T.init) of its type. |
ref | The parameter is an input/output parameter, passed by reference. |
scope | The parameter must not escape the function call (e.g. by being assigned to a global variable). Ignored for any parameter that is not a reference type. |
return | Parameter may be returned or copied to the first parameter, but otherwise does not escape from the function. Such copies are required not to outlive the argument(s) they were derived from. Ignored for parameters with no references. See Scope Parameters. |
lazy | argument is evaluated by the called function and not by the caller |
Type Constructor | Description |
const | argument is implicitly converted to a const type |
immutable | argument is implicitly converted to an immutable type |
shared | argument is implicitly converted to a shared type |
inout | argument is implicitly converted to an inout type |
In Parameters
The parameter is an input to the function. Input parameters behave as if they have the const storage class.
Note: The following requires the -preview=in switch and a compiler compliant withdmd v2.094.0 or higher.
in parameters also behave like scope parameters.
const(int[]) g(in int[] a) @safe { a[0]++; return a; }
Input parameters may also be passed by reference by the compiler. Unlike ref parameters, in parameters can bind to both lvalues and rvalues (such as literals).
- Types that would trigger a side effect if passed by value (such as types with a copy constructor, postblit, or destructor) will always be passed by reference.
- Types which cannot be copied (e.g. if their copy constructor is marked as@disable) will always be passed by reference.
- Dynamic arrays, classes, associative arrays, function pointers, and delegates will always be passed by value.
Implementation Defined: If the type of the parameter does not fall in one of those categories, whether or not it is passed by reference is implementation defined, and the backend is free to choose the method that will best fit the ABI of the platform.
Ref and Out Parameters
By default, parameters take rvalue arguments. A ref parameter takes an lvalue argument, so changes to its value will operate on the caller's argument.
void inc(ref int x) { x += 1; }
void seattle() { int z = 3; inc(z); assert(z == 4); }
A ref parameter can also be returned by reference, seeReturn Ref Parameters.
An out parameter is similar to a ref parameter, except it is initialized by default construction upon function invocation.
void zero(out int x) { assert(x == 0); }
void two(out int x) { x = 2; }
void tacoma() { int a = 3; zero(a); assert(a == 0);
int y = 3;
two(y);
assert(y == 2);
}
For dynamic array and class object parameters, which are always passed by reference, out and ref apply only to the reference and not the contents.
Lazy Parameters
An argument to a lazy parameter is not evaluated before the function is called. The argument is only evaluated if/when the parameter is evaluated within the function. Hence, a lazy argument can be executed 0 or more times.
import std.stdio : writeln;
void main() { int x; 3.times(writeln(x++)); writeln("-"); writeln(x); }
void times(int n, lazy void exp) { while (n--) exp(); }
prints to the console:
0 1 2 − 3
A lazy parameter cannot be an lvalue.
The underlying delegate of the lazy parameter may be extracted by using the & operator:
void test(lazy int dg) { int delegate() dg_ = &dg; assert(dg_() == 7); assert(dg == dg_()); }
void main() { int a = 7; test(a); }
A lazy parameter of type void can accept an argument of any type.
See Also: Lazy Variadic Functions
Default Arguments
Function parameter declarations can have default values:
void foo(int x, int y = 3) { ... } ... foo(4);
Default parameters are resolved and semantically checked in the context of the function declaration.
module m; private immutable int b; pure void g(int a = b) {}
import m; int b; pure void f() { g(); }
The attributes of the AssignExpression are applied where the default expression is used.
module m; int b; pure void g(int a = b) {}
import m; enum int b = 3; pure void f() { g(); }
See also: function type aliaseswith default values.
Return Ref Parameters
Return ref parameters are used withref functions to ensure that the returned reference will not outlive the matching argument's lifetime.
ref int identity(return ref int x) { return x; }
ref int fun() { int x; return identity(x); }
ref int gun(return ref int x) { return identity(x); }
Returning the address of a ref variable is also checked in @safe code.
int* pluto(ref int i) @safe { return &i; }
int* mars(return ref int i) @safe { return &i; }
If a function returns void, and the first parameter is ref or out, then all subsequent return ref parameters are considered as being assigned to the first parameter for lifetime checking.
void f(ref scope int* p, return ref int i) @safe { p = &i; }
void main() @safe { int i; int* p; f(p, i); *p = 5; assert(i == 5);
int j;
}
The this reference parameter to a struct non-static member function is considered the first parameter.
struct S { private int* p;
void f(return ref int i) scope @safe
{
p = &i; }
}
void main() @safe { int i; S s; s.f(i); *s.p = 2; assert(i == 2); }
If there are multiple return ref parameters, the lifetime of the return value is the smallest lifetime of the corresponding arguments.
Neither the type of the return ref parameter(s) nor the type of the return value is considered when determining the lifetime of the return value.
It is not an error if the return type does not contain any indirections.
int mercury(return ref int i) { return i; }
Template functions, auto functions, nested functions and lambdas can deduce the return attribute.
@safe:
ref int templateFunction()(ref int i) { return i; }
ref auto autoFunction(ref int i) { return i; }
void uranus() { ref int nestedFunction(ref int i) { return i; } auto lambdaFunction = (ref int i) { return &i; }; }
Struct Return Methods
Struct non-static methods can be marked with the return attribute to ensure a returned reference will not outlive the struct instance.
struct S { private int x; ref int get() return { return x; } }
ref int escape() { S s; return s.get(); }
The hidden this ref-parameter then becomes return ref.
The return attribute can also be used to limit the lifetime of the returned value, even when the method is not ref:
struct S { private int i; int* get() return @safe => &i; }
void f() @safe { int* p; { S s; int *q = s.get(); p = s.get(); p = (new S).get(); } }
Scope Parameters
A scope parameter of reference type must not escape the function call (e.g. by being assigned to a global variable). It has no effect for non-reference types.scope escape analysis is only done for @safe functions. For other functions scope semantics must be manually enforced.
Note: @safe escape analysis is only done with the -preview=dip1000 switch.
@safe:
int* gp; void thorin(scope int*); void gloin(int*); int* balin(scope int* q, int* r) { gp = q; gp = r; thorin(q); thorin(r); gloin(q); gloin(r); return q; return r; }
Implementation Defined: As a scope parameter must not escape, the compiler can potentially avoid heap-allocating a unique argument to a scope parameter. Due to this, passing an array literal, delegate literal or a NewExpression to a scope parameter may be allowed in a@nogc context.
Return Scope Parameters
Parameters marked as return scope that contain indirections can only escape those indirections via the function's return value.
@safe:
int* gp; void thorin(scope int*); void gloin(int*); int* balin(return scope int* p) { gp = p; thorin(p); gloin(p); return p; }
Class references are considered pointers that are subject to scope.
@safe:
class C { } C gp; void thorin(scope C); void gloin(C); C balin(return scope C p, scope C q, C r) { gp = p; gp = q; gp = r; thorin(p); thorin(q); thorin(r); gloin(p); gloin(q); gloin(r); return p; return q; return r; }
return scope can be applied to the this of class and interface member functions.
class C { C bofur() return scope { return this; } }
Template functions, auto functions, nested functions andlambdas can deduce the return scope attribute.
Ref Return Scope Parameters
It is not possible to have both return ref and return scope semantics for the same parameter. When a parameter is passed by ref and has both the return and scope storage classes, it gets return scope semantics if and only if the return and scope keywords appear adjacent to each other, in that order. Specifying a return ref and scope parameter enables returning a reference to a scope pointer. In all other cases, the parameter has return ref semantics and regular scope semantics.
U xerxes( ref return scope V v) U sargon(return ref scope V v) struct S {
U xerxes() return scope; U sargon() scope return; U xerxes() return const scope; }
Example of combinations of return scope, return ref, and scope semantics:
@safe:
int* globalPtr;
struct S { int val; int* ptr;
this(return scope ref int* p) { ptr = p; }
int* retRefA() scope return {
globalPtr = this.ptr; return &this.val; }
ref int retRefB() scope return {
globalPtr = this.ptr; return this.val; }
int* retScopeA() return scope {
return &this.val; return this.ptr; }
ref int retScopeB() return scope {
return this.val; return *this.ptr; }
ref int* retRefScopeC() scope return {
return this.ptr; }
}
int* retRefA(return ref scope S s) { globalPtr = s.ptr; return &s.val; }
ref int retRefB(return ref scope S s) { globalPtr = s.ptr; return s.val; }
int* retScopeA(ref return scope S s) { return &s.val; return s.ptr; }
ref int retScopeB(ref return scope S s) { return s.val; return *s.ptr; }
ref int* retRefScopeC(return ref scope int* p) { return p; }
Inferred scope parameters in pure functions
When a parameter is not marked or inferred scope, it may still be @safe to assign it a scope pointer in a function call. The following conditions need to be met:
- The function is pure, hence the argument cannot be assigned to a global variable
- The function is nothrow, hence the argument cannot be assigned to a thrown Exception object
- None of the other parameters have mutable indirections, hence the argument cannot be assigned to a longer-lived variable
Then, the parameter is still treated as scope or return scope depending on the return type of the function:
- If the function returns by ref or has a return type that contains pointers, the argument could be returned, so it is treated as return scope
- Otherwise, the argument cannot escape the function, so it is treated as scope
@safe:
int dereference(int* x) pure nothrow; int* identity(int* x) pure nothrow; int* identityThrow(int* x) pure; void assignToRef(int* x, ref int* escapeHatch) pure nothrow; void assignToPtr(int* x, int** escapeHatch) pure nothrow; void cannotAssignTo(int* x, const ref int* noEscapeHatch) pure nothrow;
int* globalPtr;
int* test(scope int* ptr) { int result = dereference(ptr); int* ptr2 = identity(ptr); int* ptr3 = identityThrow(ptr); assignToRef(ptr, globalPtr); assignToPtr(ptr, &globalPtr); cannotAssignTo(ptr, globalPtr); return ptr2; }
User-Defined Attributes for Parameters
See also: User-Defined Attributes
Variadic Functions
Variadic Functions take a variable number of arguments. There are three forms:
C-style Variadic Functions
A C-style variadic function is declared with a parameter ... as the last function parameter. It has non-D linkage, such as extern (C).
To access the variadic arguments, import the standard library module core.stdc.stdarg.
import core.stdc.stdarg;
extern (C) void dry(int x, int y, ...); void spin() { dry(3, 4); dry(3, 4, 6.8); dry(2); }
There must be at least one non-variadic parameter declared.
extern (C) int def(...);
C-style variadic functions match the C calling convention for variadic functions, and can call C Standard library functions like printf.
extern (C) int printf(const(char)*, ...);
void main() { printf("hello world\n"); }
C-style variadic functions cannot be marked as @safe.
void wash() { rinse(3, 4, 5); }
import core.stdc.stdarg; extern (C) void rinse(int x, int y, ...) { va_list args; va_start(args, y); int z; va_arg(args, z); va_end(args); }
D-style Variadic Functions
D-style variadic functions have D linkage and ... as the last parameter.
... can be the only parameter.
If there are parameters preceding the ... parameter, there must be a comma separating them from the ....
Note: If the comma is ommitted, it is a TypeSafe Variadic Function.
int abc(char c, ...); int def(...); int ghi(int i ...);
Two hidden arguments are passed to the function:
- void* _argptr
- TypeInfo[] _arguments
_argptr is a reference to the first of the variadic arguments. To access the variadic arguments, import core.vararg. Use _argptr in conjunction with core.va_arg:
import core.vararg;
void test() { foo(3, 4, 5); }
@system void foo(int x, int y, ...) { int z = va_arg!int(_argptr); }
_arguments gives the number of arguments and the typeid of each, enabling type safety to be checked at run time.
import std.stdio;
void main() { Foo f = new Foo(); Bar b = new Bar();
writefln("%s", f);
printargs(1, 2, 3L, 4.5, f, b);
}
class Foo { int x = 3; } class Bar { long y = 4; }
import core.vararg;
@system void printargs(int x, ...) { writefln("%d arguments", _arguments.length); for (int i = 0; i < _arguments.length; i++) { writeln(_arguments[i]);
if (_arguments[i] == typeid(int))
{
int j = va_arg!(int)(_argptr);
writefln("\t%d", j);
}
else if (_arguments[i] == typeid(long))
{
long j = va_arg!(long)(_argptr);
writefln("\t%d", j);
}
else if (_arguments[i] == typeid(double))
{
double d = va_arg!(double)(_argptr);
writefln("\t%g", d);
}
else if (_arguments[i] == typeid(Foo))
{
Foo f = va_arg!(Foo)(_argptr);
writefln("\t%s", f);
}
else if (_arguments[i] == typeid(Bar))
{
Bar b = va_arg!(Bar)(_argptr);
writefln("\t%s", b);
}
else
assert(0);
}
}
which prints:
0x00870FE0 5 arguments int 2 long 3 double 4.5 Foo 0x00870FE0 Bar 0x00870FD0
D-style variadic functions cannot be marked as @safe.
Typesafe Variadic Functions
A typesafe variadic function has D linkage and a variadic parameter declared as either an array or a class. The array or class is constructed from the arguments, and is passed as an array or class object.
For dynamic arrays:
int sum(int[] ar ...) { int s; foreach (int x; ar) s += x; return s; }
import std.stdio;
void main() { writeln(stan()); writeln(ollie()); }
int stan() { return sum(1, 2, 3) + sum(); }
int ollie() { int[3] ii = [4, 5, 6]; return sum(ii); }
For static arrays, the number of arguments must match the array dimension.
int sum(int[3] ar ...) { int s; foreach (int x; ar) s += x; return s; }
int frank() { return sum(2, 3); return sum(1, 2, 3); }
int dave() { int[3] ii = [4, 5, 6]; int[] jj = ii; return sum(ii); return sum(jj); }
Deprecated:
For class objects:
int tesla(int x, C c ...) { return x + c.x; }
class C { int x; string s;
this(int x, string s)
{
this.x = x;
this.s = s;
}
}
void edison() { C g = new C(3, "abc"); tesla(1, c); tesla(1, 4, "def"); tesla(1, 5); }
The lifetime of the variadic class object or array instance ends at the end of the function.
C orville(C c ...) { return c; }
int[] wilbur(int[] a ...) { return a; return a[0..1]; return a.dup; }
Implementation Defined: the variadic object or array instance may be constructed on the stack.
For other types, the argument is passed by value.
int neil(int i ...) { return i; }
void buzz() { neil(3); neil(3, 4); int[] x; neil(x); }
Lazy Variadic Functions
If the variadic parameter of a function is an array of delegates with no parameters, then each of the arguments whose type does not match that of the delegate is converted to a delegate of that type.
void hal(scope int delegate()[] dgs ...);
void dave() { int delegate() dg; hal(1, 3+x, dg, cast(int delegate())null); hal( { return 1; }, { return 3+x; }, dg, null ); }
The variadic delegate array differs from using a lazy variadic array. With the latter each array element access would evaluate every array element. With the former, only the element being accessed would be evaluated.
import std.stdio;
void main() { int x; ming(++x, ++x);
int y;
flash(++y, ++y);
}
void ming(lazy int[] arr...) { writeln(arr[0]); writeln(arr[1]); }
void flash(scope int delegate()[] arr ...) { writeln(arr0); writeln(arr1); }
Best Practices: Use scope when declaring the array of delegates parameter. This will prevent a closure being generated for the delegate, as scope means the delegate will not escape the function.
Hidden Parameters
- Non-static member functions all have a hidden parameter called thethis reference, which refers to the object for which the function is called.
- D-style variadic functions havehidden parameters.
- Functions with Objective-C linkage have an additional hidden, unnamed, parameter which is the selector it was called with.
Ref Scope Return Cases
Definitions
Definitions
Term | Description |
---|---|
I | type that contains no indirections |
P | type that contains indirections |
X | type that may or may not contain indirections |
p | parameter of type P |
i | parameter of type I |
ref | ref or out parameter |
returned | returned via the return statement |
escaped | stored in a global or other memory not in the function’s stack frame |
Classification
A parameter must be in one of the following states:
Classification
Term | Description |
---|---|
None | p may be returned or escaped |
ReturnScope | p may be returned but not escaped |
Scope | p may be neither returned nor escaped |
Ref | p may be returned or escaped,ref may not be returned nor escaped |
ReturnRef | p may be returned or escaped,ref may be returned but not escaped |
RefScope | p may be neither returned nor escaped,ref may not be returned nor escaped |
ReturnRef-Scope | p may be neither returned nor escaped,ref may be returned but not escaped |
Ref-ReturnScope | p may be returned but not escaped,ref may not be returned nor escaped |
ReturnRef-ReturnScope | p may be returned but not escaped,ref may be returned but not escaped. This isn't expressible with the current syntax and so is not allowed. |
Mapping Syntax Onto Classification
The juxtaposition of return immediately preceding scope means ReturnScope. Otherwise, return and ref in any position means ReturnRef.
Mapping
Example | Classification | Comments |
---|---|---|
X foo(P p) | None | |
X foo(scope P p) | Scope | |
P foo(return scope P p) | ReturnScope | |
I foo(return scope P p) | Scope | The return is dropped because the return type I contains no pointers. |
P foo(return P p) | ReturnScope | Makes no sense to have return without scope. |
I foo(return P p) | Scope | The return is dropped because the return type I contains no pointers. |
X foo(ref P p) | Ref | |
X foo(ref scope P p) | RefScope | |
P foo(ref return scope P p) | Ref-ReturnScope | |
P foo(return ref scope P p) | ReturnRef-Scope | |
I foo(ref return scope P p) | RefScope | |
P foo(ref return P p) | ReturnRef | |
I foo(ref return P p) | Ref | |
ref X foo(P p) | None | |
ref X foo(scope P p) | Scope | |
ref X foo(return scope P p) | ReturnScope | |
ref X foo(return P p) | ReturnScope | Makes no sense to have return without scope. |
ref X foo(ref P p) | Ref | |
ref X foo(ref scope P p) | RefScope | |
ref X foo(ref return scope P p) | Ref-ReturnScope | |
ref X foo(return ref scope P p) | ReturnRef-Scope | |
ref X foo(ref return P p) | ReturnRef | |
X foo(I i) | None | |
X foo(scope I i) | None | |
X foo(return scope I i) | None | |
X foo(return I i) | None | |
X foo(ref I i) | Ref | |
X foo(ref scope I i) | Ref | |
X foo(ref return scope I i) | Ref | |
P foo(ref return I i) | ReturnRef | |
I foo(ref return I i) | Ref | |
ref X foo(I i) | None | |
ref X foo(scope I i) | None | |
ref X foo(return scope I i) | None | |
ref X foo(return I i) | None | |
ref X foo(ref I i) | Ref | |
ref X foo(ref scope I i) | Ref | |
ref X foo(ref return scope I i) | ReturnRef | |
ref X foo(ref return I i) | ReturnRef |
Member Functions
Member functions are rewritten as if the this parameter is the first parameter of a non-member function,
struct S { X foo(); }
is treated as:
X foo(ref S);
and:
class C { X foo() }
is treated as:
X foo(P)
P and ref
The rules account for switching between ref and P, such as:
int* foo(return ref int i) { return &i; } ref int foo(int* p) { return *p; }
Covariance
Covariance means a parameter with restrictions can be converted to a parameter with fewer restrictions. This is deducible from the description of each state.
Note: ref is not covariant with non-ref, so those entries are omitted from the table for simplicity.
Covariance
From\To | None | ReturnScope | Scope |
---|---|---|---|
None | ✔ | ||
ReturnScope | ✔ | ✔ | |
Scope | ✔ | ✔ | ✔ |
Ref Covariance
From\To | Ref | ReturnRef | RefScope | ReturnRef-Scope | Ref-ReturnScope |
---|---|---|---|---|---|
Ref | ✔ | ✔ | |||
ReturnRef | ✔ | ||||
RefScope | ✔ | ✔ | ✔ | ✔ | ✔ |
ReturnRef-Scope | ✔ | ✔ | |||
Ref-ReturnScope | ✔ | ✔ | ✔ |
For example, scope matches all non-ref parameters, and ref scope matches all ref parameters.
Local Variables
Local variables are declared within the scope of a function. Function parameters are included.
A local variable cannot be read without first assigning it a value.
Implementation Defined: The implementation may not always be able to detect these cases.
The address of or reference to a local non-static variable cannot be returned from the function.
A local variable and a label in the same function cannot have the same name.
A local variable cannot hide another local variable in the same function.
Rationale: whenever this is done it often is a bug or at least looks like a bug.
ref double func(int x) { int x; double y; { char y; int z; } { wchar z; } z: return y; }
Local Static Variables
Local variables in functions declared as static, shared static or __gshared are statically allocated rather than being allocated on the stack. The lifetime of __gshared and shared static variables begins when the function is first executed and ends when the program ends. The lifetime of static variables begins when the function is first executed within the thread and ends when that thread terminates.
import std.stdio : writeln;
void foo() { static int n; if (++n == 100) writeln("called 100 times"); }
The initializer for a static variable must be evaluatable at compile time. There are no static constructors or static destructors for static local variables.
Although static variable name visibility follows the usual scoping rules, the names of them must be unique within a particular function.
void main() { { static int x; } { static int x; } { int i; } { int i; } }
Nested Functions
Functions may be nested within other functions:
int bar(int a) { int foo(int b) { int abc() { return 1; }
return b + abc();
}
return foo(a);
}
void test() { int i = bar(3); }
Nested functions can be accessed only if the name is in scope.
void foo() { void A() { B(); C(); } void B() { A(); void C() { void D() { A(); B(); C(); D(); } } } A(); B(); C(); }
and:
int bar(int a) { int foo(int b) { return b + 1; } int abc(int b) { return foo(b); } return foo(a); }
void test() { int i = bar(3); int j = bar.foo(3); }
Nested functions have access to the variables and other symbols defined by the lexically enclosing function. This access includes both the ability to read and write them.
int bar(int a) { int c = 3;
int foo(int b)
{
b += c; c++; return b + c; }
c = 4;
int i = foo(a); return i + c; }
void test() { int i = bar(3); }
This access can span multiple nesting levels:
int bar(int a) { int c = 3;
int foo(int b)
{
int abc()
{
return c; }
return b + c + abc();
}
return foo(3);
}
Static nested functions cannot access any stack variables of any lexically enclosing function, but can access static variables. This is analogous to how static member functions behave.
int bar(int a) { int c; static int d;
static int foo(int b)
{
b = d; b = c; return b + 1;
}
return foo(a);
}
Functions can be nested within member functions:
struct Foo { int a;
int bar()
{
int c;
int foo()
{
return c + a;
}
return 0;
}
}
Nested functions always have the D function linkage type.
Declaration Order
Unlike module level declarations, declarations within function scope are processed in order. This means that two nested functions cannot mutually call each other:
void test() { void foo() { bar(); } void bar() { foo(); } }
There are several workarounds for this limitation:
- Declare the functions to be static members of a nested struct:
void test() { static struct S { static void foo() { bar(); } static void bar() { foo(); } }
S.foo(); }
- Declare one or more of the functions to be function templates even if they take no specific template arguments:
void test() { void foo()() { bar(); } void bar() { foo(); } }
- Declare the functions inside of a mixin template:
mixin template T() { void foo() { bar(); } void bar() { foo(); } }
void main() { mixin T!(); }
- Use a delegate:
void test() { void delegate() fp; void foo() { fp(); } void bar() { foo(); } fp = &bar; }
Nested functions cannot be overloaded.
Function Pointers, Delegates and Closures
Function Pointers
A function pointer is declared with the function keyword:
void f(int); void function(int) fp = &f;
A function pointer can point to a static nested function:
int function() fp;
void test()
{
static int a = 7;
static int foo() { return a + 3; }
fp = &foo;
}
void main() { assert(!fp); test(); int i = fp(); assert(i == 10); }
Implementation Defined: Two functions with identical bodies, or two functions that compile to identical assembly code, are not guaranteed to have distinct function pointer values. The implementation may merge functions bodies into one if they compile to identical code.
int abc(int x) { return x + 1; } uint def(uint y) { return y + 1; }
int function(int) fp1 = &abc; uint function(uint) fp2 = &def;
Delegates & Closures
A delegate can be set to a non-static nested function:
int delegate() dg;
void test() { int a = 7; int foo() { return a + 3; }
dg = &foo;
int i = dg(); }
void main() { test(); int i = dg(); assert(i == 10); }
The stack variables referenced by a nested function are still valid even after the function exits (NOTE this is different from D 1.0). This combining of the environment and the function is called a dynamic closure.
Those referenced stack variables that make up the closure are allocated on the GC heap, unless:
- The closure is passed to a scope parameter.
- The closure is an initializer for a scope variable.
- The closure is assigned to a scope variable.
@nogc: void f(scope int delegate()); void g(int delegate());
void main()
{
int i;
int h() { return i; }
h(); scope x = &h; x(); f(&h);
f(() => i); scope d = () => i; d = () => i + 1; f(d);
}
Note: Returning addresses of stack variables, however, is not a closure and is an error.
Method Delegates
Delegates to non-static nested functions contain two pieces of data: the pointer to the stack frame of the lexically enclosing function (called the context pointer) and the address of the function. This is analogous to struct/class non-static member function delegates consisting of a this pointer and the address of the member function. Both forms of delegates are indistinguishable, and are the same type.
A delegate can be set to a particular object and method using &obj.method:
struct Foo { int a; int get() { return a; } }
int add1(int delegate() dg) { return dg() + 1; }
void main() { Foo f = {7}; int delegate() dg = &f.get; assert(dg.ptr == &f); assert(dg.funcptr == &Foo.get);
int i = add1(dg);
assert(i == 8);
int x = 27;
int abc() { return x; }
i = add1(&abc);
assert(i == 28);
}
The .ptr property of a delegate will return the_context pointer_ value as a void*.
The .funcptr property of a delegate will return the_function pointer_ value as a function type.
Initialization
Function pointers are zero-initialized by default. They can be initialized to the address of any function (including a function literal). Initialization with the address of a function that requires a context pointer is not allowed in @safe functions.
Undefined Behavior: Calling a function pointer that was set to point to a function that requires a context pointer.
struct S { static int sfunc(); int member(); }
@safe void sun() { int function() fp = &S.sfunc; fp(); fp = &S.member; }
@system void moon() { int function() fp = &S.member; fp(); }
Delegates are zero-initialized by default. They can be initialized by taking the address of a non-static member function, but a context pointer must be supplied. They can be initialized by taking the address of a non-static nested function or function literal, where the context pointer will be set to point to the stack frame, closure, or null.
Delegates cannot be initialized by taking the address of a global function, a static member function, or a static nested function.
struct S { static int sfunc(); int member() { return 1; } }
void main() { S s; int delegate() dg = &s.member; assert(dg() == 1);
int moon() { return 2; }
dg = &moon; assert(dg() == 2);
static int mars() { return 3; }
dg = () { return 4; }; assert(dg() == 4);
}
The last assignment uses a FunctionLiteral, whichis inferred as a delegate.
Note: Function pointers can be passed to functions taking a delegate argument by passing them through the std.functional.toDelegate template, which converts any callable to a delegate with a null context pointer.
Anonymous Functions and Anonymous Delegates
See FunctionLiterals.
main() Function
For console programs, main() serves as the entry point. It gets called after all the module initializers are run, and after any unittests are run. After it returns, all the module destructors are run.main() must be declared as follows:
MainFunction: MainReturnDecl main() FunctionBody MainReturnDecl main(string[] Identifier) FunctionBody
MainReturnDecl: void int noreturn auto
- If main returns void, the OS will receive a zero value on success.
- If main returns void or noreturn, the OS will receive a non-zero value on abnormal termination, such as an uncaught exception.
- If main is declared as auto, the inferred return type must be one of void, int and noreturn.
If the string[] parameter is declared, the parameter will hold arguments passed to the program by the OS. The first argument is typically the executable name, followed by any command-line arguments.
Note: The runtime can remove any arguments prefixed --DRT-.
Note: The aforementioned return / parameter types may be annotated with const,immutable. They may also be replaced by enum's with matching base types.
The main function must have D linkage.
Attributes may be added as needed, e.g. @safe, @nogc, nothrow, etc.
extern(C) main() Function
Programs may define an extern(C) main function as an alternative to the standard entry point. This form is required forBetterC programs.
A C main function must be declared as follows:
CMainFunction: extern (C) MainReturnDecl main(CmainParametersopt) BlockStatement
CmainParameters: int Identifier, char** Identifier int Identifier, char** Identifier, char** Identifier
When defined, the first two parameters denote a C-style array (length + pointer) that holds the arguments passed to the program by the OS. The third parameter is a POSIX extension called environ and holds information about the current environment variables.
Note: The exemption for storage classes / enum's defined for a D main function also applies to C main functions.
This function takes the place of the C main function and is executed immediately without any setup or teardown associated with a D main function. Programs reliant on module constructors, module destructors, or unittests need to manually perform (de)initialization using the appropriate runtime functions.
Implementation Defined: Other system-specific entry points may exist, such asWinMain and DllMain on Windows systems.
Note: Programs targeting platforms which require a different signature for main can use a function with explicit mangling:
pragma(mangle, "main") int myMain(int a, int b, int c) { return 0; }
Function Templates
Functions can have compile time arguments in the form of a template. See function templates.
Compile Time Function Execution (CTFE)
In contexts where a compile time value is required, functions can be used to compute those values. This is called Compile Time Function Execution, or CTFE.
These contexts are:
- initialization of a static variable or amanifest constant
- static initializers of struct/class members
- dimension of a static array
- argument for a template value parameter
- static if
- static foreach
- static assert
- mixin statement
- pragma argument
- __traits argument
enum eval(alias arg) = arg;
int square(int i) { return i * i; }
void main() { import std.stdio;
static j = square(3); writeln(j);
assert(square(4) == 16); static assert(square(3) == 9); writeln(eval!(square(5))); }
The function must have a SpecifiedFunctionBody.
CTFE is subject to the following restrictions:
- Expressions may not reference any global or local static variables.
- AsmStatements are not permitted
- Non-portable casts (eg, from int[] to float[]), including casts which depend on endianness, are not permitted. Casts between signed and unsigned types are permitted.
- Reinterpretation of overlapped fields in a union is not permitted.
- Undefined behavior must not occur.
Pointers are permitted in CTFE, provided they are used safely:
- Pointer arithmetic is permitted only on pointers which point to static or dynamic array elements. A pointer may also point to the first element past the array, although such pointers cannot be dereferenced. Pointer arithmetic on pointers which are null, or which point to a non-array, is not allowed.
- Ordered comparison (<, <=, >, >=) between two pointers is permitted when both pointers point to the same array, or when at least one pointer is null.
- Pointer comparisons between discontiguous memory blocks are illegal, unless two such comparisons are combined using && or || to yield a result which is independent of the ordering of memory blocks. Each comparison must consist of two pointer expressions compared with <, <=, >, or >=, and may optionally be negated with !. For example, the expression (p1 > q1 && p2 <= q2) is permitted when p1, p2 are expressions yielding pointers to memory block _P_, and q1, q2 are expressions yielding pointers to memory block _Q_, even when _P_ and _Q_ are unrelated memory blocks. It returns true if [p1..p2] lies inside [q1..q2], and false otherwise. Similarly, the expression (p1 < q1 || p2 > q2) is true if[p1..p2] lies outside [q1..q2], and false otherwise.
- Equality comparisons (==, !=, is, !is) are permitted between all pointers, without restriction.
- Any pointer may be cast to void* and from void* back to its original type. Casting between pointer and non-pointer types is illegal.
The above restrictions apply only to expressions which are actually executed. For example:
static int y = 0;
int countTen(int x) { if (x > 10) ++y; return x; }
static assert(countTen(6) == 6); static assert(countTen(12) == 12);
The __ctfe boolean pseudo-variable evaluates to true during CTFE but false otherwise.
Note: __ctfe can be used to provide an alternative execution path to avoid operations which are forbidden in CTFE. Every usage of __ctfe is statically evaluated and has no run-time cost.
Non-recoverable errors (such as assert failures) are illegal.
Implementation Defined: Executing functions via CTFE can take considerably longer than executing it at run time. If the function goes into an infinite loop, it may cause the compiler to hang.
Implementation Defined: Functions executed via CTFE can give different results from run time when implementation-defined occurs.
String Mixins and Compile Time Function Execution
All functions that execute in CTFE must also be executable at run time. The compile time evaluation of a function does the equivalent of running the function at run time. The semantics of a function cannot depend on compile time values of the function. For example:
int foo(string s) { return mixin(s); }
const int x = foo("1");
is illegal, because the runtime code for foo cannot be generated.
Best Practices: A function template, where s is a template argument, would be the appropriate method to implement this sort of thing.
No-GC Functions
No-GC functions are functions marked with the @nogc attribute. Those functions do not allocate memory on the GC heap. These operations are not allowed in No-GC functions:
- constructing an array on the heap
- resizing an array by writing to its .length property
- array concatenation
- array appending
- constructing an associative array
- indexing an associative array
Rationale: Indexing may throw a RangeError if the specified key is not present. - allocating an object with new on the heap
Note: new declarations of class types in function scopes are compatible with@nogc if used to initialize a scope variable, as they result in allocations on the stack. - calling functions that are not @nogc, unless the call is in a ConditionalStatement controlled by a DebugCondition
@nogc void foo()
{
auto a = ['a']; a.length = 1; a = a ~ a; a ~= 'c';
auto aa = ["x":1]; aa["abc"];
auto p = new int; scope auto p = new GenericClass(); bar(); debug bar(); }
void bar() { }
No-GC functions can only use a closure if it is scope - see Delegates & Closures.
@nogc int delegate() foo() { int n; return (){ return n; } }
@nogc affects the type of the function. A @nogc function is covariant with a non-@nogc function.
void function() fp;
void function() @nogc gp;
void foo();
@nogc void bar();
void test() { fp = &foo; fp = &bar; gp = &foo; gp = &bar; }
Best Practices: Since a function marked @nogc will not do any GC allocations, that implies it will not cause any GC collections to run. However, another thread may still allocate with the GC and trigger a collection. The recommended way to prevent GC collections from being run is to callcore.memory.GC.disable() instead. This will stop collections from being run in any thread until a corresponding call to core.memory.GC.enable() is run. GC allocations can still be performed when GC.disable() is in effect.
Function Safety
Safe Functions
Safe functions are marked with the @safe attribute.@safe can be inferred, see Function Attribute Inference.
Safe functions have safe interfaces. An implementation must enforce this by restricting the function's body to operations that are known to be safe, except for calls to @trusted functions.
The following restrictions are enforced by the compiler in safe functions:
- No casting from a pointer type T to any type U with pointers, except when:
- T implicitly converts to U
- U implements class or interfaceT, and both types are extern(D)
- T.opCast!U is @safe
- Both types are dynamic arrays, or both types are raw pointers, and:
* The target element type is not a pointer type
* The target element type is not mutable when the source element type is a pointer type
* Either the target element type is a dynamic array, or the target element type is no larger than the source element type
* Any source element type modifiers implicitly convert to the target element type modifiers
* Neither element type is a function type
* The target element type is not mutable when the source type is void[]
* The target element type only stores safe values
* The source element type only stores safe values when the target element type is mutable
* Neither element type is opaque.
- No casting from any non-pointer type to a pointer type.
- No pointer arithmetic (including pointer indexing & slicing).
- Cannot access array .ptr property.
- Cannot access union fields that:
- Have pointers or references overlapping with other types
- Have invariants overlapping with other types
- Overlap with fields that could become unsafe values
- Calling any System Functions.
- No catching of exceptions that are not derived fromclass Exception.
- Inline assembler must be marked as@trusted.
- No explicit casting (except with a matching @safe opCast) of:
- mutable objects to immutable (except basic data types)
- immutable objects to mutable (except basic data types)
- thread local objects to shared
- shared objects to thread local
- Cannot access @system or __gshared variables.
- Cannot use void initializers for types containing:
- Pointers/reference types
- Types with invariants
- Unsafe values
Note: When indexing or slicing an array, an out of bounds access will cause a runtime error.
Functions nested inside safe functions default to being safe functions.
Safe functions are covariant with trusted or system functions.
Best Practices: Mark as many functions @safe as practical.
Safe External Functions
External functions don't have a function body visible to the compiler:
@safe extern (C) void play();
and so safety cannot be verified automatically.
Best Practices: Explicitly set an attribute for external functions rather than relying on default settings.
Trusted Functions
Trusted functions are marked with the @trusted attribute.
Like safe functions, trusted functions have safe interfaces. Unlike safe functions, this is not enforced by restrictions on the function body. Instead, it is the responsibility of the programmer to ensure that the interface of a trusted function is safe.
Example:
immutable(int)* f(int* p) @trusted { version (none) p[2] = 13;
version (none) p[1] = 13;
version (none) return cast(immutable) p;
int* p2 = new int;
*p2 = 42;
return cast(immutable) p2;
}
void main() @safe { int[2] a = [10, 20]; int* mp = &a[0]; immutable(int)* ip = f(mp); assert(a[1] == 20); assert(ip !is mp); }
Trusted functions may call safe, trusted, or system functions.
Trusted functions are covariant with safe or system functions.
Best Practices: Trusted functions should be kept small so that they are easier to manually verify.
System Functions
System functions are functions not marked with @safe or@trusted and are not nested inside @safe functions. System functions may be marked with the @system attribute. A function being system does not mean it actually is unsafe, it just means that its safety must be manually verified.
System functions are not covariant with trusted or safe functions.
System functions can call safe and trusted functions.
Best Practices: When in doubt, mark extern (C) and extern (C++) functions as@system when their implementations are not in D, as the D compiler will be unable to check them. Most of them are @safe, but will need to be manually checked.
Best Practices: The number and size of system functions should be minimized. This minimizes the work necessary to manually check for safety.
Safe Interfaces
When a function call's arguments, any context and accessible globals each have safe values with safe aliasing, that function has a safe interface when:
- it cannot exhibitundefined behavior, and
- it cannot create unsafe values that are accessible from@safe code (e.g., via return values, global variables, or ref parameters), and
- it cannot introduce unsafe aliasing that is accessible from@safe code.
Functions that meet these requirements may be@safe or@trusted. Function that do not meet these requirements can only be@system.
Examples:
- C's free does not have a safe interface:
extern (C) @system void free(void* ptr);
because free(p) invalidates p, making its value unsafe.free can only be @system. - C's strlen and memcpy do not have safe interfaces:
extern (C) @system size_t strlen(char* s);
extern (C) @system void* memcpy(void* dst, void* src, size_t nbytes);
because they iterate pointers based on unverified assumptions (strlen assumes that s is zero-terminated; memcpy assumes that the memory objects pointed to by dst and src are at least nbytes big). Any function that traverses a C string passed as an argument can only be@system. Any function that trusts a separate parameter for array bounds can only be @system. - C's malloc does have a safe interface:
extern (C) @trusted void* malloc(size_t sz);
It does not exhibit undefined behavior for any input. It returns either a valid pointer, which is safe, or null which is also safe. It returns a pointer to a fresh allocation, so it cannot introduce any unsafe aliasing.
Note: The implementation of malloc is most likely @system code. - A D version of memcpy can have a safe interface:
@safe void memcpy(E)(E[] src, E[] dst)
{
import std.math : min;
foreach (i; 0 .. min(src.length, dst.length))
{
dst[i] = src[i];
}
}
because the rules for safe values ensure that the lengths of the arrays are correct.
Safe Values
A variable or field marked as @system does not hold a safe value, regardless of its type.
For a bool, only 0 and 1 are safe values.
For all other basic data types, all possible bit patterns are safe.
A pointer is a safe value when it is one of:
- null
- it points to a memory object that is live and the pointed to value in that memory object is safe.
Examples:
int* n = null; int* x = cast(int*) 0xDEADBEEF;
import core.stdc.stdlib: malloc, free; int* p1 = cast(int*) malloc(int.sizeof); free(p1); int** p2 = &p1; p1 = null;
A dynamic array is safe when:
- its pointer is safe, and
- its length is in-bounds with the corresponding memory object, and
- all its elements are safe.
Examples:
int[] f() @system { bool b = true; (cast(ubyte) &b) = 0xAA; int[3] a; int[] d1 = a[0 .. 3]; int[] d2 = a.ptr[0 .. 4]; int*[] d3 = [cast(int*) 0xDEADBEEF]; return d1; }
A static array is safe when all its elements are safe. Regardless of the element type, a static array with length zero is always safe.
An associative array is safe when all its keys and elements are safe.
A struct/union instance is safe when:
- the values of its accessible fields are safe, and
- it does not introduce unsafe aliasing with unions.
Examples:
void fun() { struct S { int* p; } S s1 = S(new int); S s2 = S(cast(int*) 0xDEADBEEF);
union U { int* p; size_t x; }
U u = U(new int);
}
A class reference is safe when it is null or:
- it refers to a valid class instance of the class type or a type derived from the class type, and
- the values of the instance's accessible fields are safe, and
- it does not introduce unsafe aliasing with unions.
A function pointer is safe when it is null or it refers to a valid function that has the same or a covariant signature.
A delegate is safe when:
- its .funcptr property is null or refers to a function that matches or is covariant with the delegate type, and
- its .ptr property is null or refers to a memory object that is in a form expected by the function.
Safe Aliasing
When one memory location is accessible with two different types, that aliasing is considered safe if:
- both types are const or immutable; or
- one of the types is mutable while the other is a const-qualifiedbasic data type; or
- both types are mutable basic data types; or
- one of the types is a static array type with length zero; or
- one of the types is a static array type with non-zero length, and aliasing of the array's element type and the other type is safe; or
- both types are pointer types, and aliasing of the target types is safe, and the target types have the same size.
All other cases of aliasing are considered unsafe.
Note: Safe aliasing may be exposed to functions withsafe interfaces without affecting their guaranteed safety. Unsafe aliasing does not guarantee safety.
Note: Safe aliasing does not imply that all aliased views of the data have safe values. Those must be examined separately for safety.
Examples:
void f1(ref ubyte x, ref float y) @safe { x = 0; y = float.init; } union U1 { ubyte x; float y; } void test1() { U1 u1; f1(u1.x, u1.y); }
void f2(ref int* x, ref int y) @trusted { x = new int; y = 0xDEADBEEF; } union U2 { int* x; int y; } void test2() { U2 u2; version (none) f1(u2.x, u2.y); }
Function Attribute Inference
FunctionLiterals,Auto Functions,Auto Ref Functions,nested functions andfunction templates, since their function bodies are always present, infer the following attributes unless specifically overridden:
- pure
- nothrow
- @safe
- @nogc
- return ref parameters
- scope parameters
- return scope parameters
- ref return scope parameters
Attribute inference is not done for other functions, even if the function body is present.
The inference is done by determining if the function body follows the rules of the particular attribute.
Cyclic functions (i.e. functions that wind up directly or indirectly calling themselves) are inferred as being impure, throwing, and @system.
If a function attempts to test itself for those attributes, then the function is inferred as not having those attributes.
Rationale: Function attribute inference greatly reduces the need for the user to add attributes to functions, especially for templates.
Uniform Function Call Syntax (UFCS)
A free function can be called like a member function when both:
- The member function does not (or cannot) exist for the object expression
- The free function's first parameter type matches the object expression
The object expression can be any type. This is called a UFCS function call.
void sun(T, int);
void moon(T t) { t.sun(1); }
Rationale: This provides a way to add external functions to a class as if they were public final member functions. This enables minimizing the number of functions in a class to only the essentials that are needed to take care of the object's private state, without the temptation to add a kitchen-sink's worth of member functions. It also enables function chaining and component programming.
This general idea has different names in other programming languages. The following is a short list:
- C#: Extension methods
- Kotlin: Extension functions
- Zig: Dot notation
A more complex example:
stdin.byLine(KeepTerminator.yes) .map!(a => a.idup) .array .sort .copy(stdout.lockingTextWriter());
is the equivalent of:
copy(sort(array(map!(a => a.idup)(byLine(stdin, KeepTerminator.yes)))), lockingTextWriter(stdout));
UFCS works with @property functions:
@property prop(X thisObj); @property prop(X thisObj, int value);
X obj; obj.prop; obj.prop = 1;
Functions declared in a local scope are not found when searching for a matching UFCS function. Neither are other local symbols, although local imports are searched:
module a;
void foo(X); alias boo = foo;
void main()
{
void bar(X); import b : baz;
X obj;
obj.foo(); obj.baz();
import b : boo = baz;
obj.boo(); }
Member functions are not found when searching for a matching UFCS function.
class C
{
void mfoo(X); static void sbar(X); import b : ibaz = baz;
void test()
{
X obj;
obj.ibaz(); }
}
Otherwise, UFCS function lookup proceeds normally.
Rationale: Local function symbols are not considered by UFCS to avoid unexpected name conflicts. See below for problematic examples.
int front(int[] arr) { return arr[0]; }
void main()
{
int[] a = [1,2,3];
auto x = a.front();
auto front = x; auto y = a.front(); }
class C { int[] arr; int front() { return arr.front(); } }
)
Copyright © 1999-2025 by the D Language Foundation | Page generated byDdoc on Fri May 2 11:41:56 2025