- Grammar
- Function Contracts
- Function Return Values
- Functions Without Bodies
- Pure Functions
- Nothrow Functions
- Ref Functions
- Auto Functions
- Auto Ref Functions
- Inout Functions
- Optional Parentheses
- Property Functions
- Virtual Functions
- Inline Functions
- Function Overloading
- Function Parameters
- Local Variables
- Nested Functions
- Delegates, Function Pointers, and Closures
- main() Function
- Function Templates
- Compile Time Function Execution (CTFE)
- No-GC Functions
- Function Safety
- Function Attribute Inference
- Uniform Function Call Syntax (UFCS)
Functions
Grammar
Function declaration
FuncDeclaration: StorageClassesopt BasicType FuncDeclarator FunctionBody AutoFuncDeclaration AutoFuncDeclaration: StorageClasses Identifier FuncDeclaratorSuffix FunctionBody FuncDeclarator: TypeSuffixesopt Identifier FuncDeclaratorSuffix FuncDeclaratorSuffix: Parameters MemberFunctionAttributesopt TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt
Parameters
Parameters: ( ParameterListopt ) ParameterList: Parameter Parameter , ParameterList VariadicArgumentsAttributes ... Parameter: ParameterAttributesopt BasicType Declarator ParameterAttributesopt BasicType Declarator ... ParameterAttributesopt BasicType Declarator = AssignExpression ParameterAttributesopt Type ParameterAttributesopt Type ... ParameterAttributes: InOut UserDefinedAttribute ParameterAttributes InOut ParameterAttributes UserDefinedAttribute ParameterAttributes InOut: auto TypeCtor final in lazy out ref return ref scope VariadicArgumentsAttributes: VariadicArgumentsAttribute VariadicArgumentsAttribute VariadicArgumentsAttributes VariadicArgumentsAttribute: const immutable return scope shared
Function attributes
FunctionAttributes: FunctionAttribute FunctionAttribute FunctionAttributes FunctionAttribute: FunctionAttributeKwd Property MemberFunctionAttributes: MemberFunctionAttribute MemberFunctionAttribute MemberFunctionAttributes MemberFunctionAttribute: const immutable inout return shared FunctionAttribute
Function body
FunctionBody: SpecifiedFunctionBody MissingFunctionBody ShortenedFunctionBody FunctionLiteralBody: SpecifiedFunctionBody SpecifiedFunctionBody: doopt BlockStatement FunctionContractsopt InOutContractExpression doopt BlockStatement FunctionContractsopt InOutStatement do BlockStatement MissingFunctionBody: ; FunctionContractsopt InOutContractExpression ; FunctionContractsopt InOutStatement ShortenedFunctionBody: => AssignExpression ;
Examples:
int hasSpecifiedBody() { return 1; } int hasMissingBody(); int hasShortenedBody() => 1;
Note: The ShortenedFunctionBody form requires the -preview=shortenedMethods command-line switch, which is available starting in v2.096.0.
Function Contracts
FunctionContracts: FunctionContract FunctionContract FunctionContracts FunctionContract: InOutContractExpression InOutStatement InOutContractExpression: InContractExpression OutContractExpression InOutStatement: InStatement OutStatement InContractExpression: in ( AssertArguments ) OutContractExpression: out ( ; AssertArguments ) out ( Identifier ; AssertArguments ) InStatement: in BlockStatement OutStatement: out BlockStatement out ( Identifier ) BlockStatement
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
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 it is not, the program enters an Invalid State.
The expression form is:
in (expression) in (expression, "failure string") { ...function body... }
The block statement form is:
in { ...contract preconditions... } do { ...function body... }
Postconditions
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 it is not, the program enters an Invalid State.
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.
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) { // function body }
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 { // function body }
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
Function return values not marked as ref are considered to be rvalues. This means they cannot be passed by reference to other functions.
Functions Without Bodies
Functions without bodies:
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.
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.
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.
A weakly pure function has parameters with mutable indirections. Program state can be modified transitively through the matching argument.
pure int 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 int foo(immutable(int)[] arr, int num, S val) { //arr[num] = 1; // compile error num = 2; // has no side effect to the caller side val.x = 3.14; // ditto return arr.length; }
A strongly pure function can call a weakly pure function.
Pure functions can modify the local state of the function.
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
A pure function can perform impure operations in statements that are in a ConditionalStatement controlled by a DebugCondition.
A pure function can throw exceptions.
import std.stdio; int x; immutable int y; const int* pz; pure int foo(int i, char* p, const char* q, immutable int* s) { debug writeln("in foo()"); // ok, impure code allowed in debug statement x = i; // error, modifying global state i = x; // error, reading mutable global state i = y; // ok, reading immutable global state i = *pz; // error, reading const global state return i; }
Nested functions inside a pure function are implicitly marked as pure.
pure int foo(int x, immutable int y) { int bar() // implicitly marked as pure, to be "weakly pure" // since hidden context pointer to foo stack context is mutable { x = 10; // can access states in enclosing scope // through the mutable context pointer return x; } pragma(msg, typeof(&bar)); // int delegate() pure int baz() immutable // qualifies hidden context pointer with immutable, // and has no other parameters, therefore "strongly pure" { //return x; // error, cannot access mutable data // through the immutable context pointer return y; // ok } // can call pure nested functions return bar() + baz(); }
A pure factory function is a strongly pure function that returns a result that has mutable indirections. All mutable memory returned by the call may not be referenced by any other part of the program, i.e. it is newly allocated by the function. Nor may the mutable references of the result refer to any object that existed before the function call. 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, result); return result; }
All references in make's result refer to other List objects created by make, and no other part of the program refers to any of these objects. This restriction does not apply to any Exception or Error thrown from the function.
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
Ref functions allow functions to return by reference, meaning that the return value must be an lvalue, and the lvalue is returned, not the rvalue.
ref int foo() { auto p = new int(2); return *p; } ... int i = foo(); // i is set to 2 foo() = 3; // reference returns can be lvalues
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; // error, escaping a reference to local variable i }
A ref parameter may not be returned by ref.
ref int moon(ref int i) { return i; // error }
Auto Functions
Auto functions have their return type inferred from any ReturnStatements in the function body.
An auto function is declared without a return type. If it does not already have a storage class, use the auto storage class.
If there are multiple ReturnStatements, the types of them must be implicitly convertible to a common type. If there are no ReturnStatements, the return type is inferred to be void.
auto foo(int x) { return x + 3; } // inferred to be int auto bar(int x) { return x; return 2.5; } // inferred to be double
Auto Ref Functions
Auto ref functions infer their return type just as auto functions do. In addition, they become ref functions if all return expressions are lvalues, and it would not be a reference to a local or a parameter.
auto ref f1(int x) { return x; } // value return auto ref f2() { return 3; } // value return auto ref f3(ref int x) { return x; } // ref return auto ref f4(out int x) { return x; } // ref return auto ref f5() { static int x; return x; } // ref return
The ref-ness of a function is determined from all ReturnStatements in the function body:
auto ref f1(ref int x) { return 3; return x; } // ok, value return auto ref f2(ref int x) { return x; return 3; } // ok, value return auto ref f3(ref int x, ref double y) { return x; return y; // The return type is deduced to double, but cast(double)x is not an lvalue, // then become a value return. }
Auto ref function can have explicit return type.
auto ref int (ref int x) { return x; } // ok, ref return auto ref int foo(double x) { return x; } // error, cannot convert double to int
Inout Functions
Functions that differ only in whether the parameters are mutable, const or immutable, and have corresponding mutable, const or immutable return types, can be combined into one function using the inout type constructor. Consider the following overload set:
int[] slice(int[] a, int x, int y) { return a[x .. y]; } const(int)[] slice(const(int)[] a, int x, int y) { return a[x .. y]; } immutable(int)[] slice(immutable(int)[] a, int x, int y) { return a[x .. y]; }
The code generated by each of these functions is identical. The inout type constructor can combine them into one function:
inout(int)[] slice(inout(int)[] a, int x, int y) { return a[x .. y]; }
The inout keyword forms a wildcard that stands in for mutable, const, immutable, inout, or inout const. When calling the function, the inout state of the return type is changed to match that of the argument type passed to the inout parameter.
inout can also be used as a type constructor inside a function that has a parameter declared with inout. The inout state of a type declared with inout is changed to match that of the argument type passed to the inout parameter:
inout(int)[] asymmetric(inout(int)[] input_data) { inout(int)[] r = input_data; while (r.length > 1 && r[0] == r[$-1]) r = r[1..$-1]; return r; }
Inout types can be implicitly converted to const or inout const, but to nothing else. Other types cannot be implicitly converted to inout. Casting to or from inout is not allowed in @safe functions.
void f(inout int* ptr) { const int* p = ptr; int* q = ptr; // error immutable int* r = ptr; // error }
Matching an inout Parameter
A set of arguments to a function with inout parameters is considered a match if any inout argument types match exactly, or:
- No argument types are composed of inout types.
- A mutable, const or immutable argument type can be matched against each corresponding parameter inout type.
If such a match occurs, inout is considered the common qualifier of the matched qualifiers. If more than two parameters exist, the common qualifier calculation is recursively applied.
mutable | const | immutable | inout | inout const | |
mutable (= m) | m | c | c | c | c |
const (= c) | c | c | c | c | c |
immutable (= i) | c | c | i | wc | wc |
inout (= w) | c | c | wc | w | wc |
inout const (= wc) | c | c | wc | wc | wc |
The inout in the return type is then rewritten to match the inout qualifiers:
int[] ma; const(int)[] ca; immutable(int)[] ia; inout(int)[] foo(inout(int)[] a) { return a; } void test1() { // inout matches to mutable, so inout(int)[] is // rewritten to int[] int[] x = foo(ma); // inout matches to const, so inout(int)[] is // rewritten to const(int)[] const(int)[] y = foo(ca); // inout matches to immutable, so inout(int)[] is // rewritten to immutable(int)[] immutable(int)[] z = foo(ia); } inout(const(int))[] bar(inout(int)[] a) { return a; } void test2() { // inout matches to mutable, so inout(const(int))[] is // rewritten to const(int)[] const(int)[] x = bar(ma); // inout matches to const, so inout(const(int))[] is // rewritten to const(int)[] const(int)[] y = bar(ca); // inout matches to immutable, so inout(int)[] is // rewritten to immutable(int)[] immutable(int)[] z = bar(ia); }
Note: Shared types cannot be matched with inout.
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 a property function.
void foo() {} // no arguments void fun(int x = 10) { } void bar(int[] arr) {} // for UFCS void main() { foo(); // OK foo; // also OK fun; // OK int[] arr; arr.bar(); // OK arr.bar; // also OK }
Optional parentheses are not applied to delegates or function pointers.
void main() { int function() fp; assert(fp == 6); // Error, incompatible types int function() and int assert(*fp == 6); // Error, incompatible types int() and int int delegate() dg; assert(dg == 6); // Error, incompatible types int delegate() and int }
If a function returns a delegate or function pointer, the parentheses are required if the returned value is to be called.
struct S { int function() callfp() { return &numfp; } int delegate() calldg() return { return &numdg; } int numdg() { return 6; } } int numfp() { return 6; } void main() { S s; int function() fp; fp = s.callfp; assert(fp() == 6); fp = s.callfp(); assert(fp() == 6); int x = s.callfp()(); assert(x == 6); int delegate() dg; dg = s.calldg; assert(dg() == 6); dg = s.calldg(); assert(dg() == 6); int y = s.calldg()(); 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; } // read property @property int data(int value) { return m_data = value; } // write property private: int m_data; }
To use it:
int test() { Foo f; f.data = 3; // same as f.data(3); return f.data + 3; // same as 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, unless they are final.
Struct and union member functions are not virtual.
Static member functions are not virtual.
Member functions which are private or package are not virtual.
Member template functions are not virtual.
Member functions with Objective-C linkage are virtual even if marked with final or static.
class A { int def() { ... } final int foo() { ... } final private int bar() { ... } private int abc() { ... } } class B : A { override int def() { ... } // ok, overrides A.def override int foo() { ... } // error, A.foo is final int bar() { ... } // ok, A.bar is final private, but not virtual int abc() { ... } // ok, A.abc is not virtual, B.abc is virtual } void test(A a) { a.def(); // calls B.def a.foo(); // calls A.foo a.bar(); // calls A.bar a.abc(); // calls A.abc } void func() { B b = new B(); test(b); }
The 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 { // overrides and is covariant with Foo.test() override B test() { return null; } }
Virtual functions all have a hidden parameter called the this reference, which refers to the class object instance for which the function is called.
Functions with Objective-C linkage have an additional hidden, unnamed, parameter which is the selector it was called with.
To directly call a base member function, insert base class name before the member function name. For example:
class B { int foo() { return 1; } } class C : B { override int foo() { return 2; } void test() { assert(B.foo() == 1); // translated to this.B.foo(), and // calls B.foo statically. assert(C.foo() == 2); // calls C.foo statically, even if // the actual instance of 'this' is D. } } class D : C { override int foo() { return 3; } } void main() { auto d = new D(); assert(d.foo() == 3); // calls D.foo assert(d.B.foo() == 1); // calls B.foo assert(d.C.foo() == 2); // calls C.foo d.test(); }
Function Inheritance and Overriding
A function in a derived class with the same name and covariant with a function in a base class overrides that function:
class A { int foo(int x) { ... } } class B : A { override int foo(int x) { ... } } void test() { B b = new B(); bar(b); } void bar(A a) { a.foo(1); // calls B.foo(int) }
When doing overload resolution, the functions in the base class are not considered, as they are not in the same Overload 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); // calls B.foo(long), since A.foo(int) not considered A a = b; a.foo(1); // issues runtime error (instead of calling A.foo(int)) }
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() { B b = new B(); bar(b); } void bar(A a) { a.foo(1); // calls A.foo(int) B b = new B(); b.foo(1); // calls A.foo(int) }
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 { void set(long i) { } } void foo(A a) { int i; a.set(3); // error, use of A.set(int) is hidden by B // use 'alias set = A.set;' to introduce base class overload set assert(i == 1); } void main() { foo(new B); }
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(); // calls A.foo(5) B b = new B(); b.foo(); // calls B.foo(7) C c = new C(); c.foo(); // error, need an argument for C.foo }
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)); // prints "void delegate() pure nothrow @safe" in compile time }
The attributes @disable and deprecated are not allowed on overriding functions.
class B { void foo() {} } class D : B { @disable override void foo() {} // error, can't apply @disable to overriding function }
Static functions with Objective-C linkage are overridable.
Inline Functions
The compiler makes the decision whether to inline a function or not. This decision may be controlled by pragma(inline).
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 is qualifier-convertible to the parameter type)
- exact match
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.
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; /* Both foo(A) and foo(B) match with implicit conversions (level 2). * Applying partial ordering rules, * foo(B) cannot be called with an A, and foo(A) can be called * with a B. Therefore, foo(B) is more specialized, and is selected. */ foo(c); // calls foo(B) }
A function with a variadic argument is considered less specialized than a function without.
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(); // calls A.foo() foo(i); // calls A.foo(long) foo(c); // calls B.foo(C) foo(1,2); // error, does not match any foo foo(1); // error, matches A.foo(long) and B.foo(int) A.foo(1); // calls A.foo(long) }
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(); // calls A.foo() foo(1L); // calls A.foo(long) foo(c); // calls B.foo(C) foo(1,2); // error, does not match any foo foo(1); // calls B.foo(int) A.foo(1); // calls A.foo(long) }
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, input/output, and 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.
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
Note: The following requires the -preview=in switch, available in v2.094.0 or higher. When not in use, in is equivalent to const.The parameter is an input to the function. Input parameters behave as if they have the const scope storage classes. 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 copy constructor, postblit, or destructor), and 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.
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, see Return Ref Parameters.
An out parameter is similar to a ref parameter, except it is initialized with x.init 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
Function Default Arguments
Function parameter declarations can have default values:
void foo(int x, int y = 3) { ... } ... foo(4); // same as foo(4, 3);
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(); // ok, uses m.b }
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(); // error, cannot access mutable global `m.b` in pure function }
If the default value for a parameter is given, all following parameters must also have default values.
Return Ref Parameters
Return ref parameters are used with ref functions to ensure that the returned reference will not outlive the matching argument's lifetime.
ref int identity(return ref int x) { return x; // pass-through function that does nothing } ref int fun() { int x; return identity(x); // Error: escaping reference to local variable x } ref int gun(return ref int x) { return identity(x); // OK }
Struct non-static methods marked with the return attribute ensure the 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(); // Error: escaping reference to local variable s }
Returning the address of a ref variable is also checked.
int* pluto(ref int i) { return &i; // error: returning &i escapes a reference to parameter i } int* mars(return ref int i) { return &i; // ok }
If the 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. The this reference parameter to a struct non-static member function is considered the first parameter.
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; // ok }
Template functions, auto functions, nested functions and lambdas can deduce the return attribute.
ref int templateFunction()(ref int i) { return i; // ok } ref auto autoFunction(ref int i) { return i; // ok } void uranus() { ref int nestedFunction(ref int i) { return i; // ok } } void venus() { auto lambdaFunction = (ref int i) { return &i; // ok }; }
inout ref parameters imply the return attribute.
inout(int)* neptune(inout ref int i) { return &i; // ok }
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.
@safe: int* gp; void thorin(scope int*); void gloin(int*); int* balin(scope int* q, int* r) { gp = q; // error, q escapes to global gp gp = r; // ok thorin(q); // ok, q does not escape thorin() thorin(r); // ok gloin(q); // error, gloin() escapes q gloin(r); // ok that gloin() escapes r return q; // error, cannot return 'scope' q return r; // ok }
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, depending on the compiler implementation.
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; // error, p escapes to global gp thorin(p); // ok, p does not escape thorin() gloin(p); // error, gloin() escapes p return p; // ok }
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; // error, p escapes to global gp gp = q; // error, q escapes to global gp gp = r; // ok thorin(p); // ok, p does not escape thorin() thorin(q); // ok thorin(r); // ok gloin(p); // error, gloin() escapes p gloin(q); // error, gloin() escapes q gloin(r); // ok that gloin() escapes r return p; // ok return q; // error, cannot return 'scope' q return r; // ok }
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 and lambdas can deduce the return scope attribute.
Ref Return Scope Parameters
Parameters marked as ref return scope come in two forms:
U xerxes(ref return scope V v); // (1) ref and return scope ref U sargon(ref return scope V v); // (2) return ref and scope
The first form attaches the return to the scope, and has return scope parameter semantics for the value of the ref parameter.
The second form attaches the return to the ref, and has return ref parameter semantics with additional scope parameter semantics.
Although a struct constructor returns a reference to the instance being constructed, it is treated as form (1).
The lexical order of the attributes ref, return, and scope is not significant.
It is not possible to have both return ref and return scope semantics for the same parameter.
@safe: struct S { this(return scope ref int* p) { ptr = p; } int val; int* ptr; } int* foo1(ref return scope S s); int foo2(ref return scope S s); ref int* foo3(ref return scope S s); ref int foo4(ref return scope S s); int* test1(scope S s) { return foo1(s); // Error: scope variable `s` may not be returned return foo3(s); // Error: scope variable `s` may not be returned } int test2(S s) { return foo2(s); return foo4(s); } ref int* test3(S s) { return foo3(s); // Error: returning `foo3(s)` escapes a reference to parameter `s` } ref int test4(S s) { return foo4(s); // Error: returning `foo4(s)` escapes a reference to parameter `s` } S test5(ref scope int* p) { return S(p); // Error: scope variable `p` may not be returned } S test6(ref return scope int* p) { return S(p); }
User-Defined Attributes for Parameters
See also: User-Defined AttributesVariadic 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, ...); // C-style Variadic Function void spin() { dry(3, 4); // ok, no variadic arguments dry(3, 4, 6.8); // ok, one variadic argument dry(2); // error, no argument for parameter y }
There must be at least one non-variadic parameter declared.
extern (C) int def(...); // error, must have at least one parameter
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); // first variadic argument is 5 } import core.stdc.stdarg; extern (C) void rinse(int x, int y, ...) { va_list args; va_start(args, y); // y is the last named parameter int z; va_arg(args, z); // z is set to 5 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 ....
int abc(char c, ...); // one required parameter: c int def(...); // no required parameters int ghi(int i ...); // a typesafe variadic function //int boo(, ...); // error
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); // first variadic argument is 5 } @system void foo(int x, int y, ...) { int z = va_arg!int(_argptr); // z is set to 5 and _argptr is advanced // to the next argument }_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); } }
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 ...) // typesafe variadic function { int s; foreach (int x; ar) s += x; return s; } import std.stdio; void main() { writeln(stan()); // 6 writeln(ollie()); // 15 } int stan() { return sum(1, 2, 3) + sum(); // returns 6+0 } int ollie() { int[3] ii = [4, 5, 6]; return sum(ii); // returns 15 }
For static arrays, the number of arguments must match the array dimension.
int sum(int[3] ar ...) // typesafe variadic function { int s; foreach (int x; ar) s += x; return s; } int frank() { return sum(2, 3); // error, need 3 values for array return sum(1, 2, 3); // returns 6 } int dave() { int[3] ii = [4, 5, 6]; int[] jj = ii; return sum(ii); // returns 15 return sum(jj); // error, type mismatch }
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); // ok, since c is an instance of C tesla(1, 4, "def"); // ok tesla(1, 5); // error, no matching constructor for C }
The lifetime of the variadic class object or array instance ends at the end of the function.
C orville(C c ...) { return c; // error, c instance contents invalid after return } int[] wilbur(int[] a ...) { return a; // error, array contents invalid after return return a[0..1]; // error, array contents invalid after return return a.dup; // ok, since copy is made }
For other types, the argument is passed by value.
int neil(int i ...) { return i; } void buzz() { neil(3); // returns 3 neil(3, 4); // error, too many arguments int[] x; neil(x); // error, type mismatch }
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); // (1) hal( { return 1; }, { return 3+x; }, dg, null ); // same as (1) }
The variadic delegate array differs from using a lazy variadic array. With the former each array element access would evaluate every array element. With the latter, only the element being accessed would be evaluated.
import std.stdio; void main() { int x; ming(++x, ++x); int y; flash(++y, ++y); } // lazy variadic array void ming(lazy int[] arr...) { writeln(arr[0]); // 1 writeln(arr[1]); // 4 } // variadic delegate array void flash(scope int delegate()[] arr ...) { writeln(arr[0]); // 1 writeln(arr[1]); // 2 }
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.
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.
ref double func(int x) { int x; // error, hides previous definition of x double y; { char y; // error, hides previous definition of y int z; } { wchar z; // Ok, previous z is out of scope } z: // error, z is a local variable and a label return y; // error, returning ref to local }
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.
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; } // error { int i; } { int i; } // ok }
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); // i is assigned 4 }
Nested functions can be accessed only if the name is in scope.
void foo() { void A() { B(); // error, B() is forward referenced C(); // error, C undefined } void B() { A(); // ok, in scope void C() { void D() { A(); // ok B(); // ok C(); // ok D(); // ok } } } A(); // ok B(); // ok C(); // error, C undefined }and:
int bar(int a) { int foo(int b) { return b + 1; } int abc(int b) { return foo(b); } // ok return foo(a); } void test() { int i = bar(3); // ok int j = bar.foo(3); // error, bar.foo not visible }
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; // 4 is added to b c++; // bar.c is now 5 return b + c; // 12 is returned } c = 4; int i = foo(a); // i is set to 12 return i + c; // returns 17 } void test() { int i = bar(3); // i is assigned 17 }
This access can span multiple nesting levels:
int bar(int a) { int c = 3; int foo(int b) { int abc() { return c; // access bar.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; // ok b = c; // error, foo() cannot access frame of bar() 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.
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(); } // error, bar not defined void bar() { foo(); } // ok }
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(); } // ok static void bar() { foo(); } // ok } S.foo(); // compiles (but note the infinite runtime loop) }
void test() { void foo()() { bar(); } // ok (foo is a function template) void bar() { foo(); } // ok }
mixin template T() { void foo() { bar(); } // ok void bar() { foo(); } // ok } void main() { mixin T!(); }
void test() { void delegate() fp; void foo() { fp(); } void bar() { foo(); } fp = &bar; }
Nested functions cannot be overloaded.
Delegates, Function Pointers, and Closures
A function pointer can point to a static nested function:
int function() fp; // fp is a pointer to a function returning an int void test() { static int a = 7; static int foo() { return a + 3; } fp = &foo; } void bar() { test(); int i = fp(); // i is set to 10 }
int abc(int x) { return x + 1; } uint def(uint y) { return y + 1; } int delegate(int) fp1 = &abc; uint delegate(uint) fp2 = &def; // Do not rely on fp1 and fp2 being different values; the compiler may merge // them.
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(); // i is set to 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 is called a closure.
Those referenced stack variables that make up the closure are allocated on the GC heap. Closures are not allowed for @nogc functions.
void bar() { int b; test(); int i = dg(); // ok, test.a is in a closure and still exists }
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.
struct Foo { int a = 7; int bar() { return a; } } int foo(int delegate() dg) { return dg() + 1; } void test() { int x = 27; int abc() { return x; } Foo f; int i; i = foo(&abc); // i is set to 28 i = foo(&f.bar); // i is set to 8 }
This combining of the environment and the function is called a dynamic closure.
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.
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.
struct S { static int sfunc(); int member(); // has hidden `this` reference parameter } @safe void sun() { int function() fp = &S.sfunc; fp(); // Ok fp = &S.member; // error } @system void moon() { int function() fp = &S.member; // Ok because @system fp(); // undefined behavior }
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; // Ok, s supplies context pointer assert(dg() == 1); dg = &S.sfunc; // error dg = &S.member; // error int moon() { return 2; } dg = &moon; // Ok assert(dg() == 2); static int mars() { return 3; } dg = &mars; // error dg = () { return 4; }; // Ok assert(dg() == 4); }
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 using one of the following forms:
- void main() { ... }
- void main(string[] args) { ... }
- int main() { ... }
- int main(string[] args) { ... }
The main function must have D linkage.
Attributes may be added as needed, e.g. @safe, @nogc, nothrow, etc.
BetterC main() Function
For BetterC programs, the main function is declared using one of the following forms:
- extern (C) int main() { ... }
- extern (C) int main(int argc, char** argv) { ... }
This takes the place of the C main function and serves the identical purpose.
Module constructors, module destructors, and unittests are not run.
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 a manifest 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(Args...) = Args[0]; int square(int i) { return i * i; } void foo() { static j = square(3); // CTFE writeln(j); assert(square(4)); // run time static assert(square(3) == 9); // CTFE writeln(eval!(square(5))); // CTFE }
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.
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; // access static variable return x; } static assert(countTen(6) == 6); // OK static assert(countTen(12) == 12); // invalid, modifies y.
The __ctfe boolean pseudo-variable evaluates to true during CTFE but false otherwise.
Non-recoverable errors (such as assert failures) are illegal.
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.
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
Note: because it may throw RangeError if the specified key is not present
- allocating an object with new on the heap
- calling functions that are not @nogc, unless the call is in a ConditionalStatement controlled by a DebugCondition
@nogc void foo() { auto a = ['a']; // (1) error, allocates a.length = 1; // (2) error, array resizing allocates a = a ~ a; // (3) error, arrays concatenation allocates a ~= 'c'; // (4) error, appending to arrays allocates auto aa = ["x":1]; // (5) error, allocates aa["abc"]; // (6) error, indexing may allocate and throws auto p = new int; // (7) error, operator new allocates bar(); // (8) error, bar() may allocate debug bar(); // (8) Ok } void bar() { }
No-GC functions cannot be closures.
@nogc int delegate() foo() { int n; // error, variable n cannot be allocated on heap return (){ return n; } // since `n` escapes `foo()`, a closure is required }
@nogc affects the type of the function. A @nogc function is covariant with a non-@nogc function.
void function() fp; void function() @nogc gp; // pointer to @nogc function void foo(); @nogc void bar(); void test() { fp = &foo; // ok fp = &bar; // ok, it's covariant gp = &foo; // error, not contravariant gp = &bar; // ok }
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 safe.
The following operations are not allowed in safe functions:
- No casting from a pointer type to any type with pointers other than void*.
- No casting from any non-pointer type to a pointer type.
- No pointer arithmetic (including pointer indexing).
- Cannot access unions that have pointers or references overlapping with other types.
- Cannot access unions that have fields with invariants overlapping with other types.
- Calling any System Functions.
- No catching of exceptions that are not derived from class Exception.
- No inline assembler.
- No explicit casting of mutable objects to immutable.
- No explicit casting of immutable objects to mutable.
- No explicit casting of thread local objects to shared.
- No explicit casting of shared objects to thread local.
- Cannot access __gshared variables.
- Cannot use void initializers for pointers.
- Cannot use void initializers for class or interface references.
- Cannot use void initializers for types that have invariants.
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.
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.
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; // Invalid. p[2] is out of bounds. This line would exhibit undefined // behavior. version (none) p[1] = 13; // Invalid. In this program, p[1] happens to be in-bounds, so the // line would not exhibit undefined behavior, but a trusted function // is not allowed to rely on this. version (none) return cast(immutable) p; // Invalid. @safe code still has mutable access and could trigger // undefined behavior by overwriting the value later on. int* p2 = new int; *p2 = 42; return cast(immutable) p2; // Valid. After f returns, no mutable aliases of p2 can exist. } void main() @safe { int[2] a = [10, 20]; int* mp = &a[0]; immutable(int)* ip = f(mp); assert(a[1] == 20); // Guaranteed. f cannot access a[1]. assert(ip !is mp); // Guaranteed. f cannot introduce unsafe aliasing. }
Trusted functions may call safe, trusted, or system functions.
Trusted functions are covariant with safe or system functions.
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.
Safe Interfaces
When it is only called with safe values and safe aliasing, a function has a safe interface when:
- it cannot exhibit undefined behavior, and
- it cannot create unsafe values that are accessible from other parts of the program (e.g., via return values, global variables, or ref parameters), and
- it cannot introduce unsafe aliasing that is accessible from other parts of the program.
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]; } }
Safe Values
For 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; /* n is safe because dereferencing null is a well-defined crash. */ int* x = cast(int*) 0xDEADBEEF; /* x is (most likely) unsafe because it is not a valid pointer and cannot be dereferenced. */ import core.stdc.stdlib: malloc, free; int* p1 = cast(int*) malloc(int.sizeof); /* p1 is safe because the pointer is valid and *p1 is safe regardless of its actual value. */ free(p1); /* This makes p1 unsafe. */ int** p2 = &p1; /* While it can be dereferenced, p2 is unsafe because p1 is unsafe. */ p1 = null; /* This makes p1 and p2 safe. */
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 { int[3] a; int[] d1 = a[0 .. 2]; /* d1 is safe. */ int[] d2 = a.ptr[0 .. 3]; /* d2 is unsafe because it goes beyond a's bounds. */ int*[] d3 = [cast(int*) 0xDEADBEEF]; /* d3 is unsafe because the element is unsafe. */ return d1; /* Up to here, d1 was safe, but its pointer becomes invalid when the function returns, so the returned dynamic array is unsafe. */ }
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:
struct S { int* p; } S s1 = S(new int); /* s1 is safe. */ S s2 = S(cast(int*) 0xDEADBEEF); /* s2 is unsafe, because s2.p is unsafe. */ union U { int* p; size_t x; } U u = U(new int); /* Even though both u.p and u.x are safe, u is unsafe because of unsafe aliasing. */
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-qualified basic 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.
Examples:
void f1(ref ubyte x, ref float y) @safe { x = 0; y = float.init; } union U1 { ubyte x; float y; } // safe aliasing U1 u1; f1(u1.x, u1.y); // Ok void f2(ref int* x, ref int y) @trusted { x = new int; y = 0xDEADBEEF; } union U2 { int* x; int y; } // unsafe aliasing U2 u2; version (none) f1(u2.x, u2.y); // not safe
Function Attribute Inference
FunctionLiterals, Auto Functions, Auto Ref Functions, nested functions and function templates, since their function bodies are always present, infer the pure, nothrow, @safe, @nogc, return ref parameters, scope parameters, return scope parameters and ref return scope parameters attributes unless specifically overridden.
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.
Uniform Function Call Syntax (UFCS)
A free function can be called with a syntax equivalent to that of a member function of its first parameter type. The free function is called a UFCS function.
void sun(T, int); void moon(T t) { t.sun(1); // If `T` does not have member function `sun`, // `t.sun(1)` is interpreted as if it were written `sun(t, 1)` }
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; // if X does not have member prop, reinterpret as prop(obj); obj.prop = 1; // similarly, reinterpret as prop(obj, 1);
Functions declared in a local scope are not found when searching for a matching UFCS function.
Member functions are not found when searching for a matching UFCS function.
Otherwise, UFCS function lookup proceeds normally.
module a; void foo(X); alias boo = foo; void main() { void bar(X); // bar declared in local scope import b : baz; // void baz(X); X obj; obj.foo(); // OK, calls a.foo; //obj.bar(); // NG, UFCS does not see nested functions obj.baz(); // OK, calls b.baz, because it is declared at the // top level scope of module b import b : boo = baz; obj.boo(); // OK, calls aliased b.baz instead of a.boo (== a.foo), // because the declared alias name 'boo' in local scope // overrides module scope name } class C { void mfoo(X); // member function static void sbar(X); // static member function import b : ibaz = baz; // void baz(X); void test() { X obj; //obj.mfoo(); // NG, UFCS does not see member functions //obj.sbar(); // NG, UFCS does not see static member functions obj.ibaz(); // OK, ibaz is an alias of baz which declared at // the top level scope of module b } }
int front(int[] arr) { return arr[0]; } void main() { int[] a = [1,2,3]; auto x = a.front(); // call .front by UFCS auto front = x; // front is now a variable auto y = a.front(); // Error, front is not a function } class C { int[] arr; int front() { return arr.front(); // Error, C.front is not callable // using argument types (int[]) } })