Functions
FunctionBody:
BlockStatement
BodyStatement
InStatement BodyStatement
OutStatement BodyStatement
InStatement OutStatement BodyStatement
OutStatement InStatement BodyStatement
InStatement:
in BlockStatement
OutStatement:
out BlockStatement
out ( Identifier ) BlockStatement
BodyStatement:
body BlockStatement
Function Return Values
Function return values 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 which cannot access global or static, mutable state save through their arguments. This can enable optimizations based on the fact that a pure function is guaranteed to mutate nothing which isn't passed to it, and in 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 an impure function cannot override a pure one
- is covariant with an impure function
- cannot perform I/O
As a concession to practicality, a pure function can:
- allocate memory via a NewExpression
- terminate the program
- 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;
}
Nothrow Functions
Nothrow functions do not throw any exceptions derived from class Exception.
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 match exactly. If there are no ReturnStatements, the return type is inferred to be void.
auto foo(int i) {
return i + 3; // return type is inferred to be int
}
Auto Ref Functions
Auto ref functions infer their return type just as auto functions do. In addition, they become ref functions if the return expression is an lvalue, and it would not be a reference to a local or a parameter.
auto ref foo(int x) { return x; } // value return
auto ref foo() { return 3; } // value return
auto ref foo(ref int x) { return x; } // ref return
auto ref foo(out int x) { return x; } // ref return
auto ref foo() { static int x; return x; } // ref return
The lexically first ReturnStatement determines the ref-ness of a function:
auto ref foo(ref int x) { return 3; return x; } // ok, value return
auto ref foo(ref int x) { return x; return 3; } // error, ref return, 3 is not an lvalue
Inout Functions
Functions that deal with mutable, const, or immutable types with equanimity often need to transmit their type to the return value:
int[] foo(int[] a, int x, int y) { return a[x .. y]; }
const(int)[] foo(const(int)[] a, int x, int y) { return a[x .. y]; }
immutable(int)[] foo(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 or immutable. When the function is called, the inout of the return type is changed to whatever the mutable, const, or immutable status of the argument type to the parameter inout was.
Inout types can be implicitly converted to const, but to nothing else. Other types cannot be implicitly converted to inout. Casting to or from inout is not allowed in @safe functions.
If an inout appears in a function parameter list, it must also appear in the return type.
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, if every match is mutable, then the inout is considered matched with mutable. If every match is immutable, then the inout is considered matched with immutable. Otherwise, the inout is considered matched with const. The inout in the return type is then rewritten to be the inout matched attribute.
Global and static variable types cannot have any inout components.
Note: Shared types are not overlooked. Shared types cannot be matched with inout.
Property Functions
Property functions are tagged with the @property attribute. They can be called without parentheses (hence acting like properties).
struct S {
int m_x;
@property {
int x() { return m_x; }
int x(int newx) { return m_x = newx; }
}
}
void foo() {
S s;
s.x = 3; // calls s.x(int)
bar(s.x); // calls bar(s.x())
}
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 aren't templatized are virtual unless the compiler can determine that they will never be overridden (e.g. they're marked with final and don't override any functions in a base class), in which case, it will make them non-virtual. 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 {
int def() { ... } // ok, overrides A.def
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 {
B test() { return null; } // overrides and is covariant with Foo.test()
}
Virtual functions all have a hidden parameter called the this reference, which refers to the class object for which the function is called.
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 functions 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 A.foo 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 core.exception.HiddenFuncError exception is raised:
import core.exception;
class A {
void set(long i) { }
void set(int i) { }
}
class B : A {
void set(long i) { }
}
void foo(A a) {
int i;
try {
a.set(3); // error, throws runtime exception since
// A.set(int) should not be available from B
}
catch (HiddenFuncError o) {
i = 1;
}
assert(i == 1);
}
void main() {
foo(new B);
}
If an HiddenFuncError exception is thrown in your program, the use of overloads and overrides needs to be reexamined in the relevant classes.
The HiddenFuncError exception is not thrown 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
}
Inline Functions
There is no inline keyword. The compiler makes the decision whether to inline a function or not, analogously to the register keyword no longer being relevant to a compiler's decisions on enregistering variables. (There is no register keyword either.)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 conversion to const
- 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. because the name mangling does 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 A.foo foo;
alias B.foo 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 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 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 | equivalent to const scope |
out | parameter is initialized upon function entry with the default value for its type |
ref | parameter is passed by reference |
scope | references in the parameter cannot be escaped (e.g. assigned to a global variable) |
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 |
void foo(out int x) {
// x is set to int.init,
// which is 0, at start of foo()
}
int a = 3;
foo(a);
// a is now 0
void abc(out int x) {
x = 2;
}
int y = 3;
abc(y);
// y is now 2
void def(ref int x) {
x += 1;
}
int z = 3;
def(z);
// z is now 4
For dynamic array and object parameters, which are passed by reference, in/out/ref apply only to the reference and not the contents.
lazy arguments are evaluated not when the function is called, but when the parameter is evaluated within the function. Hence, a lazy argument can be executed 0 or more times. A lazy parameter cannot be an lvalue.
void dotimes(int n, lazy void exp) {
while (n--)
exp();
}
void test() {
int x;
dotimes(3, writefln(x++));
}
prints to the console:
0
1
2
A lazy parameter of type void can accept an argument of any type.
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 evaluated in the context of the function declaration. If the default value for a parameter is given, all following parameters must also have default values.
Variadic 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) int 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.
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
}
int foo(int x, int y, ...) {
va_list ap;
version (X86_64)
va_start(ap, __va_argsave);
else version (X86)
va_start(ap, y); // y is the last named parameter
int z;
va_arg(ap, z); // z is set to 5
va_end(ap);
}
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 conjuction with va_arg:
import core.vararg;
void test() {
foo(3, 4, 5); // first variadic argument is 5
}
int 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 the creation of typesafe variadic functions.
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);
}
which prints:
0x00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
0x00870FE0
Bar
0x00870FD0
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 test() {
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 );
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 is never referred to. Dead variables, like anachronistic dead code, are just a source of confusion for maintenance programmers.
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
}
One solution is declare both functions as members of a nested struct. Another solution is to use a delegate:
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
}
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.
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) { ... }
Compile Time Function Execution (CTFE)
Functions which are both portable and free of side-effects can be executed at compile time. This is useful when constant folding algorithms need to include recursion and looping. Compile time function execution 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 at compile time.
- 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
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 at compile time, but false at run time, can be used to provide an alternative execution path to avoid operations which are forbidden at compile time. Every usage of __ctfe is evaluated before code generation and therefore has no run-time cost, even if no optimizer is used.
In order to be executed at compile time, the function must appear in a context where it must be so executed, for example:
- initialization of a static variable
- dimension of a static array
- argument for a template value parameter
template eval( A... ) {
const typeof(A[0]) eval = A[0];
}
int square(int i) {
return i * i;
}
void foo() {
static j = square(3); // compile time
writefln(j);
writefln(square(4)); // run time
writefln(eval!(square(5))); // compile time
}
Executing functions at compile time 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 at compile time 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 at compile time 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(char[] 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.
Function Safety
Safe functions are functions that are statically checked to exhibit no possibility of undefined behavior. Undefined behavior is often used as a vector for malicious attacks.
Safe Functions
Safe functions are marked with the @safe attribute.
The following operations are not allowed in safe functions:
- No casting from a pointer type to any type other than void*.
- No casting from any non-pointer type to a pointer type.
- No modification of pointer values.
- 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.
- 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.
- No taking the address of a local variable or function parameter.
- Cannot access __gshared variables.
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.
Trusted Functions
Trusted functions are marked with the @trusted attribute.
Trusted functions are guaranteed by the programmer to not exhibit any undefined behavior if called by a safe function. Generally, trusted functions should be kept small so that they are easier to manually verify.
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.
Function Attribute Inference
FunctionLiterals and function templates, since their function bodies are always present, infer the pure, nothrow, and @safe 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.