- Grammar
- 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: BasicType2opt 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: nothrow pure Property MemberFunctionAttributes: MemberFunctionAttribute MemberFunctionAttribute MemberFunctionAttributes MemberFunctionAttribute: const immutable inout return shared FunctionAttribute
Function body
FunctionBody: SpecifiedFunctionBody MissingFunctionBody FunctionLiteralBody: SpecifiedFunctionBody SpecifiedFunctionBody: doopt BlockStatement FunctionContractsopt InOutContractExpression doopt BlockStatement FunctionContractsopt InOutStatement do BlockStatement MissingFunctionBody: ; FunctionContractsopt InOutContractExpression ; FunctionContractsopt InOutStatement
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
Contracts
The in and out blocks or expressions of a function declaration specify the pre- and post-conditions of the function. They are used in Contract Programming. The code inside these blocks should not have any side-effects, including modifying function parameters and/or return values.
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 functions that cannot directly access global or static mutable state. pure guarantees that a pure function call won't access or modify any implicit state in the program.
Unlike other functional programming languages, D's pure functions allow modification of the caller state through their mutable parameters.
pure int foo(int[] arr) { arr[] += 1; return arr.length; } int[] a = [1, 2, 3]; foo(a); assert(a == [2, 3, 4]);
A pure function accepting parameters with mutable indirections offers what's called "weak purity" because it can change program state transitively through its arguments. A pure function that has no parameter with mutable indirections is called "strongly pure" and fulfills the purity definition in traditional functional languages. Weakly pure functions are useful as reusable building blocks for strongly pure functions.
To prevent mutation, D offers the immutable type qualifier. If all of a pure function's parameters are immutable or copied values without any indirections (e.g. int), the type system guarantees no side effects.
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; }
The maximum guarantee of pure is called "strong purity". It can enable optimizations based on the fact that a function is guaranteed to not mutate anything which isn't passed to it. For cases where the compiler can guarantee that a pure function cannot alter its arguments, it can enable full, functional purity (i.e. the guarantee that the function will always return the same result for the same arguments). To that end, a pure function:
- does not read or write any global or static mutable state
- cannot call functions that are not pure
- can override an impure function, but cannot be overridden by an impure function
- is covariant with an impure function
- cannot perform I/O
This definition of mutable functions is more general than the one traditionally employed by pure functional languages because it allows a D pure function to use state mutation, as long as all state is created internally or reachable through its arguments. In particular, a pure function may allocate memory by means of e.g. new or malloc without these being special cases. A pure function is allowed to loop indefinitely or terminate the program.
As a concession to practicality, a pure function can also:
- 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
- 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; }
An implementation may assume that a pure function that (a) accepts only parameters without mutable indirections, and (b) returns a result without mutable indirections, will have the same effect for all invocation with equivalent arguments, and is allowed to memoize the result of the function under the assumption that equivalent parameters always produce equivalent results. Such functions are termed strongly pure functions in this document. Note that a strongly pure 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.
A pure function that accepts only parameters without mutable indirections and returns a result that has mutable indirections is called a pure factory function. An implementation may assume that all mutable memory returned by the call is not referenced by any other part of the program, i.e. it is newly allocated by the function. Conversely, the mutable references of the result may be assumed to not 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; }
Here, an implementation may assume (without having knowledge of the body of make) that all references in make's result refer to other List objects created by make, and that no other part of the program refers to any of these objects.
Any pure function that is not strongly pure cannot be assumed to be memoizable, and calls to it may not be elided even if it returns void (save for compiler optimizations that prove the function has no effect). Function calls may still be elided, or results be memoized, by means of traditional inlining and optimization techniques available for all functions.
If a strongly pure function throws an exception or an error, the assumptions related to memoization and references 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
Ref functions allow functions to return by reference. This is analogous to ref function parameters.
ref int foo() { auto p = new int; return *p; } ... foo() = 3; // reference returns can be lvalues
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 deal with mutable, const, or immutable types with equanimity often need to transmit their type to the return value:
int[] f1(int[] a, int x, int y) { return a[x .. y]; } const(int)[] f2(const(int)[] a, int x, int y) { return a[x .. y]; } immutable(int)[] f3(immutable(int)[] a, int x, int y) { return a[x .. y]; }
The code generated by these three functions is identical. To indicate that these can be one function, the inout type constructor is employed:
inout(int)[] foo(inout(int)[] a, int x, int y) { return a[x .. y]; }
The inout forms a wildcard that stands in for any of mutable, const, immutable, inout, or inout const. When the function is called, the inout of the return type is changed to whatever the mutable, const, immutable, inout, or inout const status of the argument type to the parameter inout was.
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.
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, the 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 be the inout matched 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 are not overlooked. Shared types cannot be matched with inout.
Nested functions inside pure function are implicitly marked as pure.
pure int foo(int x, immutable int y) { int bar() // implicitly marked as pure, to be "weak purity" // hidden context pointer 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 // qualify hidden context pointer with immutable, // and has no other parameters, make "strong purity" { //return x; // error, cannot access mutable data // through the immutable context pointer return y; // ok } // can call pure nested functions return bar() + baz(); }
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 functions that are called indirectly through a function pointer table, called a vtbl[], rather than directly. All public and protected member functions which are non-static and are not templatized are virtual unless the compiler can determine that they will never be overridden (e.g. they are marked with final and do not override any functions in a base class), in which case, it will make them non-virtual. Static or final functions with Objective-C linkage are virtual as well. This results in fewer bugs caused by not declaring a function virtual and then overriding it anyway.
Member functions which are private or package are never virtual, and hence cannot be overridden.
Functions with non-D linkage cannot be virtual and hence cannot be overridden.
Member template functions cannot be virtual and hence cannot be overridden.
Functions marked as final may not be overridden in a derived class, unless they are also private. For example:
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); }
Covariant return types are supported, which means that the overriding function in a derived class can return a type that is derived from the type returned by 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 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 avoid dynamic binding on member function call, 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 parameter types as 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) }
However, when doing overload resolution, the functions in the base class are not considered:
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 consider 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. If, through implicit conversions to the base class, those other functions do get called, a compile-time error will be given:
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); }
If an error occurs during the compilation of your program, the use of overloads and overrides needs to be reexamined in the relevant classes.
The compiler will not give an error if the hidden function is disjoint, as far as overloading is concerned, from all the other virtual functions is the inheritance hierarchy.
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 }
If a derived class overrides a base class member function with different FunctionAttributes, the missing attributes will be automatically compensated by the compiler.
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 }
It's not allowed to mark an overridden method with the attributes @disable or deprecated. To stop the compilation or to output the deprecation message, the compiler 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() {} } void main() { B b = new D; b.foo(); // compiles and calls the most derived even if disabled. }
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), assuming that the compiler implements it, which is not mandatory.
Note that any FunctionLiteral should be inlined when used in its declaration scope.
Function Overloading
Functions are overloaded based on how well the arguments to a function can match up with the parameters. The function with the best match is selected. The levels of matching 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 pointer) 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 try 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 conversion rules. * 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.
Functions defined with non-D linkage cannot be overloaded. This is because the name mangling might not take the parameter types into account.
Overload Sets
Functions declared at the same scope overload against each other, and are called an Overload Set. A typical 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 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. An instance of foo is selected based on it matching in exactly one overload set:
import A; import B; 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); // 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, const, immutable, shared, inout or scope. For example:
int foo(in int x, out int y, ref int z, int q);
x is in, y is out, z is ref, and q is none.
- The function declaration makes it clear what the inputs and outputs to the function are.
- It eliminates the need for IDL (interface description language) as a separate language.
- It provides more information to the compiler, enabling more error checking and possibly better code generation.
Storage Class | Description |
---|---|
none | parameter becomes a mutable copy of its argument |
in | defined as scope const. However in has not yet been properly implemented so it's current implementation is equivalent to const. It is recommended to avoid using in until it is properly defined and implemented. Use scope const or const explicitly instead. |
ref | parameter is passed by reference |
out | parameter is passed by reference and initialized upon function entry with the default value for its type |
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 |
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 |
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; } int z = 3; inc(z); assert(z == 4);
A ref parameter can also be returned by reference - see: return ref parameters.
An out parameter x is similar to a ref parameter, except it is initialized with x.init upon function invocation.
void foo(out int x) { assert(x == 0); } int a = 3; foo(a); assert(a == 0); void abc(out int x) { x = 2; } int y = 3; abc(y); assert(y == 2);
For dynamic array and object parameters, which are always passed by reference, in/out/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.
Note: scope escape analysis is currently only done by dmd when the -dip1000 switch is passed.
@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 xerxes(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(return scope ref S s); int foo2(return scope ref S s); ref int* foo3(return ref scope S s); ref int foo4(return ref 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
Functions taking a variable number of arguments are called variadic functions. A variadic function can take one of three forms:
- C-style variadic functions
- Variadic functions with type info
- Typesafe variadic functions
C-style Variadic Functions
A C-style variadic function is declared as taking a parameter of ... after the required function parameters. It has non-D linkage, such as extern (C):
extern (C) void foo(int x, int y, ...); foo(3, 4); // ok foo(3, 4, 6.8); // ok, one variadic argument foo(2); // error, y is a required argument
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 is most useful for calling C library functions like printf.
C-style variadic functions cannot be marked as @safe.
Access to variadic arguments is done using the standard library module core.stdc.stdarg.
import core.stdc.stdarg; void test() { foo(3, 4, 5); // first variadic argument is 5 } void foo(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 }
D-style Variadic Functions
Variadic functions with argument and type info are declared as taking a parameter of ... after the required function parameters. It has D linkage, and need not have any non-variadic parameters declared:
int abc(char c, ...); // one required parameter: c int def(...); // ok
To access them, the following import is required:
import core.vararg;
These variadic functions have a special local variable declared for them, _argptr, which is a core.vararg reference to the first of the variadic arguments. To access the arguments, _argptr must be used in conjunction with va_arg:
import core.vararg; void test() { foo(3, 4, 5); // first variadic argument is 5 } void foo(int x, int y, ...) { int z; z = va_arg!int(_argptr); // z is set to 5 }
An additional hidden argument with the name _arguments and type TypeInfo[] is passed to the function. _arguments gives the number of arguments and the type of each, enabling type safety to be checked at run time.
import std.stdio; import core.vararg; class Foo { int x = 3; } class Bar { long y = 4; } 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); } } void main() { Foo f = new Foo(); Bar b = new Bar(); writefln("%s", f); printargs(1, 2, 3L, 4.5, f, b); }
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
Typesafe variadic functions are used when the variable argument portion of the arguments are used to construct an array or class object.
For arrays:
int main() { return sum(1, 2, 3) + sum(); // returns 6+0 } int func() { int[3] ii = [4, 5, 6]; return sum(ii); // returns 15 } int sum(int[] ar ...) { int s; foreach (int x; ar) s += x; return s; }
For static arrays:
int test() { return sum(2, 3); // error, need 3 values for array return sum(1, 2, 3); // returns 6 } int func() { int[3] ii = [4, 5, 6]; int[] jj = ii; return sum(ii); // returns 15 return sum(jj); // error, type mismatch } int sum(int[3] ar ...) { int s; foreach (int x; ar) s += x; return s; }
For class objects:
class Foo { int x; string s; this(int x, string s) { this.x = x; this.s = s; } } void test(int x, Foo f ...); ... Foo g = new Foo(3, "abc"); test(1, g); // ok, since g is an instance of Foo test(1, 4, "def"); // ok test(1, 5); // error, no matching constructor for Foo
An implementation may construct the object or array instance on the stack. Therefore, it is an error to refer to that instance after the variadic function has returned:
Foo test(Foo f ...) { return f; // error, f instance contents invalid after return } int[] test(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 built with itself, as in:
int test(int i ...) { return i; } ... test(3); // returns 3 test(3, 4); // error, too many arguments int[] x; test(x); // error, type mismatch
Lazy Variadic Functions
If the variadic parameter is an array of delegates with no parameters:
void foo(int delegate()[] dgs ...);
Then each of the arguments whose type does not match that of the delegate is converted to a delegate.
int delegate() dg; foo(1, 3+x, dg, cast(int delegate())null);
is the same as:
foo( { return 1; }, { return 3+x; }, dg, null );
The lazy variadic delegate solution is preferable to using a lazy variadic array, because each array index would evaluate every element:
import std.stdio; void foo(lazy int[] arr...) { writeln(arr[0]); // 1 writeln(arr[1]); // 4, not 2 } void main() { int x; foo(++x, ++x); }
Local Variables
It is an error to use a local variable without first assigning it a value. The implementation may not always be able to detect these cases. Other language compilers sometimes issue a warning for this, but since it is always a bug, it should be an error.
It is an error to declare a local variable that hides another local variable in the same function:
void 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; // legal, previous z is out of scope } }
While this might look unreasonable, in practice whenever this is done it either is a bug or at least looks like a bug.
It is an error to return the address of or a reference to a local variable.
It is an error to have a local variable and a label with the same name.
Local Static Variables
Local variables in functions can be declared as static or __gshared in which case they are statically allocated rather than being allocated on the stack. As such, their value persists beyond the exit of the function.
void foo() { static int n; if (++n == 100) writeln("called 100 times"); }
The initializer for a static variable must be evaluatable at compile time, and they are initialized upon the start of the thread (or the start of the program for __gshared). 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; 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 }
Note: Two functions with identical bodies, or two functions that compile to identical assembly code, are not guaranteed to have distinct function pointer values. The compiler is free to merge functions bodies into one if they compile to identical code.
int abc(int x) { return x + 1; } int def(int y) { return y + 1; } int delegate(int) fp1 = &abc; int delegate(int) 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 (this is different from D 1.0). This is called a closure. Returning addresses of stack variables, however, is not a closure and is an error.
int* bar() { int b; test(); int i = dg(); // ok, test.a is in a closure and still exists return &b; // error, bar.b not valid after bar() exits }
Delegates to non-static nested functions contain two pieces of data: the pointer to the stack frame of the lexically enclosing function (called the frame 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 interchangeable, and are actually 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 frame pointer value as a void*.
The .funcptr property of a delegate will return the function pointer value as a function type.
Functions and delegates declared at module scope are zero-initialized by default. However both can be initialized to any function pointer (including a function literal). For delegates, the context pointer .ptr is initialized to null.
int function() foo = { return 42; }; int delegate() bar = { return 43; }; int delegate() baz; void main() { assert(foo() == 42); assert(bar() == 43); assert(baz is null); }
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 without context.
Future directions: Function pointers and delegates may merge into a common syntax and be interchangeable with each other.
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) { ... }
Function Templates
Template functions are useful for avoiding code duplication - instead of writing several copies of a function, each with a different parameter type, a single function template can be sufficient. For example:
// Only one copy of func needs to be written void func(T)(T x) { writeln(x); } void main() { func!(int)(1); // pass an int func(1); // pass an int, inferring T = int func("x"); // pass a string func(1.0); // pass a float struct S {} S s; func(s); // pass a struct }
func takes a template parameter T and a runtime parameter, x. T is a placeholder identifier that can accept any type. In this case T can be inferred from the runtime argument type.
Note: Using the name T is just a convention. The name TypeOfX could have been used instead.
For more information, see function templates.
Compile Time Function Execution (CTFE)
Functions which are both portable and free of global side-effects can be executed at compile time. In certain contexts, such compile time execution is guaranteed. It is called Compile Time Function Execution (CTFE) then. The contexts that trigger CTFE 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
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 writeln(eval!(square(5))); // CTFE }
CTFE is subject to the following restrictions:
- The function source code must be available to the compiler. Functions which exist in the source code only as extern declarations cannot be executed in CTFE.
- Executed expressions may not reference any global or local static variables.
- asm statements 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:
- C-style semantics on pointer arithmetic are strictly enforced. Pointer arithmetic is permitted only on pointers which point to static or dynamic array elements. Such pointers must point to an element of the array, or to the first element past the array. Pointer arithmetic is completely forbidden on pointers which are null, or which point to a non-array.
- The memory location of different memory blocks is not defined. 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 independent memory blocks will generate
a compile-time error, 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 prohibited.
Note that 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); // OK static assert(countTen(12) == 12); // invalid, modifies y.
The __ctfe boolean pseudo-variable, which evaluates to true in CTFE, but false otherwise, can be used to provide an alternative execution path to avoid operations which are forbidden in CTFE. Every usage of __ctfe is evaluated before code generation and therefore has no run-time cost, even if no optimizer is used.
Executing functions via CTFE can take considerably longer than executing it at run time. If the function goes into an infinite loop, it will hang at compile time (rather than hanging at run time).
Non-recoverable errors (such as assert failures) do not throw exceptions; instead, they end interpretation immediately.
Functions executed via CTFE can give different results from run time in the following scenarios:
- floating point computations may be done at a higher precision than run time
- dependency on implementation defined order of evaluation
- use of uninitialized variables
These are the same kinds of scenarios where different optimization settings affect the results.
String Mixins and Compile Time Function Execution
Any 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. This means that 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. A function template 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, through the following language features:
- constructing an array on the heap
- resizing an array by writing to its .length property
- array concatenation and appending
- constructing an associative array on the heap
- indexing an associative array (because it may throw RangeError if the specified key is not present)
- allocating an object on the heap
@nogc void foo() { auto a = ['a']; // error, allocates a.length = 1; // error, array resizing allocates a = a ~ a; // error, arrays concatenation allocates a ~= 'c'; // error, appending to arrays allocates auto aa = ["x":1]; // error, allocates aa["abc"]; // error, indexing may allocate and throws auto p = new int; // error, operator new allocates }
No-GC functions cannot call functions that are not @nogc.
@nogc void foo() { bar(); // error, bar() may allocate } 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; } }
@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 }
To ease debugging, in a ConditionalStatement controlled by a DebugCondition @nogc functions can call functions that are not @nogc.
Function Safety
Safe Functions
Safe functions are marked with the @safe attribute.
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.
- Calling any system functions.
- No catching of exceptions that are not derived from class Exception.
- Disallow @system asm statements.
- 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.
- No taking the address of a local variable or function parameter.
- Cannot access __gshared variables.
- Cannot use void initializers for pointers.
- Cannot use void initializers for class or interface references.
When indexing and slicing an array, an out of bounds access will cause a runtime error, in order to prevent undefined behavior.
Functions nested inside safe functions default to being safe functions.
Safe functions are covariant with trusted or system functions.
Note: The verifiable safety of functions may be compromised by bugs in the compiler and specification. Please report all such errors so they can be corrected.
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 the compiler is unable to verify that it cannot exhibit undefined behavior.
System functions are not covariant with trusted or safe functions.
Safe Interfaces
Given that it is only called with safe values and safe aliasing, a function has a safe interface when:
- it cannot possibly 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 dst and src are at least nbytes long). Any function that traverses a C string passed as an argument can only be @system. Any function that trusts a seperate 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. - 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 safe when:
- it can be dereferenced validly, and
- the value of the pointee 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 stated type or a subtype, 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 is null or refers to a function that matches the delegate type, and
- its .ptr is null or refers to a context that is in a format expected by the function.
Safe Aliasing
When one memory location is accessible with two different types, that aliasing is considered safe in these cases:
- 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.
Note: Safe aliasing may be exposed to functions with safe interfaces without affecting their guaranteed safety. But when unsafe aliasing is exposed to such functions, their safety is no longer guaranteed.
Note: An aliasing relation being safe does not imply that both views of the data have safe values. That must be examined separately when safety is of concern.
Examples:
void f1(ref ubyte x, ref float y) @safe { x = 0; y = float.init; } union U1 { ubyte x; float y; } /* This aliasing is safe. */ U1 u1; f1(u1.x, u1.y); /* So calling f1 like this is ok. */ void f2(ref int* x, ref int y) @trusted { x = new int; y = 0xDEADBEEF; } union U2 { int* x; int y; } /* This aliasing is unsafe. */ U2 u2; version (none) f1(u2.x, u2.y); /* So calling f2 like this is not ok. */
Function Attribute Inference
FunctionLiterals and function templates, since their function bodies are always present, infer the pure, nothrow, @safe, and @nogc 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 that looks as if the function were a member function of its first parameter type.
void func(X thisObj); X obj; obj.func(); // If 'obj' does not have regular member 'func', // it's automatically rewritten to 'func(obj)'
This provides a way to add functions to a class externally as if they were public final member functions, which enables function chaining and component programming.
stdin.byLine(KeepTerminator.yes) .map!(a => a.idup) .array .sort .copy(stdout.lockingTextWriter());
It also works with @property functions:
@property prop(X thisObj); @property prop(X thisObj, int value); X obj; obj.prop; // Rewrites to: prop(obj); obj.prop = 1; // Rewrites to: prop(obj, 1);
Syntactically parenthesis-less check for @property functions is done at the same time as UFCS rewrite.
When UFCS rewrite is necessary, compiler searches the name on accessible module level scope, in order from the innermost scope.
module a; void foo(X); alias boo = foo; void main() { void bar(X); 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); static void sbar(X); 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 } }
The reason why local symbols are not considered by UFCS, is to avoid unexpected name conflicts. See below problematic examples.
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[]) } }