Functions
FuncDeclaration:
StorageClassesopt BasicType FuncDeclarator FunctionBody
AutoFuncDeclaration
AutoFuncDeclaration:
StorageClasses Identifier FuncDeclaratorSuffix FunctionBody
FuncDeclarator:
BasicType2opt Identifier FuncDeclaratorSuffix
FuncDeclaratorSuffix:
Parameters MemberFunctionAttributesopt
TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt
Parameters:
( ParameterListopt )
ParameterList:
Parameter
Parameter , ParameterList
...
Parameter:
InOutopt BasicType Declarator
InOutopt BasicType Declarator ...
InOutopt BasicType Declarator = AssignExpression
InOutopt Type
InOutopt Type ...
InOut:
InOutX
InOut InOutX
InOutX:
auto
TypeCtor
final
in
lazy
out
ref
return ref
scope
FunctionAttributes:
FunctionAttribute
FunctionAttribute FunctionAttributes
FunctionAttribute:
nothrow
pure
Property
MemberFunctionAttributes:
MemberFunctionAttribute
MemberFunctionAttribute MemberFunctionAttributes
MemberFunctionAttribute:
const
immutable
inout
return
shared
FunctionAttribute
FunctionBody:
BlockStatement
FunctionContractsopt BodyStatement
FunctionContracts
FunctionContracts:
InStatement OutStatementopt
OutStatement InStatementopt
InStatement:
in BlockStatement
OutStatement:
out BlockStatement
out ( Identifier ) BlockStatement
BodyStatement:
body BlockStatement
The in and out blocks 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 are considered to be rvalues.
This means they cannot be passed by reference to other functions.
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 are functions that cannot access global/static
mutable state, except if their arguments contain pointers to such. This
enables optimizations based on the fact that a pure function may at most
mutate state reachable through its parameters. 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()"); x = i; i = x; i = y; i = *pz; 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 can only throw exceptions derived
from class Error.
Nothrow functions are covariant with throwing ones.
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;
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; } auto foo(int x) { return x; return 2.5; }
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 foo(int x) { return x; } auto ref foo() { return 3; } auto ref foo(ref int x) { return x; } auto ref foo(out int x) { return x; } auto ref foo() { static int x; return x; }
The ref-ness of a function is determined from all
ReturnStatements in the function body:
auto ref foo(ref int x) { return 3; return x; } auto ref foo(ref int x) { return x; return 3; } auto ref foo(ref int x, ref double y)
{
return x; return y;
}
Auto ref function can have explicit return type.
auto ref int foo(ref int x) { return x; } auto ref int foo(double x) { return x; }
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, 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.
Common qualifier of the two type qualifiers | 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()
{
int[] x = foo(ma);
const(int)[] y = foo(ca);
immutable(int)[] z = foo(ia);
}
inout(const(int))[] bar(inout(int)[] a) { return a; }
void test2()
{
const(int)[] x = bar(ma);
const(int)[] y = bar(ca);
immutable(int)[] z = bar(ia);
}
Note: Shared types are not overlooked. Shared types cannot
be matched with inout.
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() {} void fun(int x = 10) { }
void bar(int[] arr) {}
void main()
{
foo(); foo; fun;
int[] arr;
arr.bar(); arr.bar; }
Optional parentheses are not applied to delegates or function pointers.
void main()
{
int function() fp;
assert(fp == 6); assert(*fp == 6);
int delegate() dg;
assert(dg == 6); }
If a function returns a delegate or function pointer, the parantheses are required if the
returned value is to be called.
struct S {
int function() callfp() { return &numfp; }
int delegate() calldg() { 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);
}
Properties are functions that can be syntactically treated
as if they were fields or variables. Properties can be read from or written to.
A property is read by calling a method or function with no arguments;
a property is written by calling a method or function with its argument
being the value it is set to.
Simple getter and setter properties can be written using UFCS.
These can be enhanced with the additon of the @property attribute to the function, which
adds the following behaviors:
- @property functions cannot be overloaded with non-@property functions with the same name.
- @property functions can only have zero, one or two parameters.
- @property functions cannot have variadic parameters.
- For the expression typeof(exp) where exp is an @property function,
the type is the return type of the function, rather than the type of the function.
- For the expression __traits(compiles, exp) where exp is an @property function,
a further check is made to see if the function can be called.
- @property are mangled differently, meaning that @property must be consistently
used across different compilation units.
- The ObjectiveC interface recognizes @property setter functions as special and modifies
them accordingly.
A simple property would be:
struct Foo
{
@property int data() { return m_data; }
@property int data(int value) { return m_data = value; }
private:
int m_data;
}
To use it:
int test()
{
Foo f;
f.data = 3; return f.data + 3; }
The absence of a read method means that the property is write-only.
The absence of a write method means that the property is read-only.
Multiple write methods can exist; the correct one is selected using
the usual function overloading rules.
In all the other respects, these methods are like any other methods.
They can be static, have different linkages, have their address taken, etc.
The built in properties .sizeof, .alignof, and .mangleof
may not be declared as fields or methods in structs, unions, classes or enums.
If a property function has no parameters, it works as a getter.
If has exactly one parameter, it works as a setter.
Virtual functions 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. 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() { ... } override int foo() { ... } int bar() { ... } int abc() { ... } }
void test(A a)
{
a.def(); a.foo(); a.bar(); 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
{
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.
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); assert(C.foo() == 2); }
}
class D : C
{
override int foo() { return 3; }
}
void main()
{
auto d = new D();
assert(d.foo() == 3); assert(d.B.foo() == 1); assert(d.C.foo() == 2); d.test();
}
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); }
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); A a = b;
a.foo(1); }
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); B b = new B();
b.foo(1); }
If such an AliasDeclaration is not used, the derived
class's functions completely override all the functions of the
same name in the base class, even if the types of the parameters
in the base class functions are different. 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); 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();
B b = new B();
b.foo();
C c = new C();
c.foo(); }
If a derived class overrides a base class member function with diferrent
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));
}
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(); }
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.
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;
foo(c); }
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.
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(); foo(1L); foo(c); foo(1,2); foo(1); A.foo(1); }
Even though B.foo(int) is a better match than A.foo(long) for foo(1),
it is an error because the two matches are in
different overload sets.
Overload sets can be merged with an alias declaration:
import A;
import B;
alias foo = A.foo;
alias foo = B.foo;
void bar(C c)
{
foo(); foo(1L); foo(c); foo(1,2); foo(1); A.foo(1); }
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.
Parameter Storage Classes 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).
Ignored for parameters with no references |
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)
{
}
int a = 3;
foo(a);
void abc(out int x)
{
x = 2;
}
int y = 3;
abc(y);
void def(ref int x)
{
x += 1;
}
int z = 3;
def(z);
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, writeln(x++));
}
prints to the console:
0
1
2
A lazy parameter of type void can accept an argument
of any type.
Function parameter declarations can have default values:
void foo(int x, int y = 3)
{
...
}
...
foo(4);
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.
Note: The return attribute is currently only enforced by dmd
when the -dip25 switch is passed.
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; }
ref int fun() {
int x;
return identity(x); }
ref int gun(return ref int x) {
return identity(x); }
Ref methods marked with the return attribute ensure the returned
reference will not outlive the respective aggregate instance.
struct S
{
private int x;
ref int get() return { return x; }
}
ref int escape()
{
S s;
return s.get(); }
Template functions and lambdas can deduce the
return attribute.
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
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); foo(3, 4, 6.8); foo(2);
There must be at least one non-variadic parameter declared.
extern (C) int def(...);
C-style variadic functions match the C calling convention for
variadic functions, and 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); }
void foo(int x, int y, ...)
{
va_list args;
va_start(args, y);
int z;
va_arg(args, z); }
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, ...); int def(...);
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); }
void foo(int x, int y, ...)
{
int z;
z = va_arg!int(_argptr); }
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);
}
which prints:
0x00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
0x00870FE0
Bar
0x00870FD0
D-style variadic functions cannot be marked as @safe.
Typesafe variadic functions 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(); }
int func()
{
int[3] ii = [4, 5, 6];
return sum(ii); }
int sum(int[] ar ...)
{
int s;
foreach (int x; ar)
s += x;
return s;
}
For static arrays:
int test()
{
return sum(2, 3); return sum(1, 2, 3); }
int func()
{
int[3] ii = [4, 5, 6];
int[] jj = ii;
return sum(ii); return sum(jj); }
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); test(1, 4, "def"); test(1, 5);
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; }
int[] test(int[] a ...)
{
return a; return a[0..1]; return a.dup; }
For other types, the argument is built with itself, as in:
int test(int i ...)
{
return i;
}
...
test(3); test(3, 4); int[] x;
test(x);
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 );
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; double y;
...
{
char y; int z;
}
{
wchar z; }
}
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 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; } { int i; }
{ int i; } }
Functions may be nested within other functions:
int bar(int a)
{
int foo(int b)
{
int abc() { return 1; }
return b + abc();
}
return foo(a);
}
void test()
{
int i = bar(3); }
Nested functions can be accessed only if the name is in scope.
void foo()
{
void A()
{
B(); C(); }
void B()
{
A(); void C()
{
void D()
{
A(); B(); C(); D(); }
}
}
A(); B(); C(); }
and:
int bar(int a)
{
int foo(int b) { return b + 1; }
int abc(int b) { return foo(b); } return foo(a);
}
void test()
{
int i = bar(3); int j = bar.foo(3); }
Nested functions have access to the variables and other symbols
defined by the lexically enclosing function.
This access includes both the ability to read and write them.
int bar(int a)
{
int c = 3;
int foo(int b)
{
b += c; c++; return b + c; }
c = 4;
int i = foo(a); return i + c; }
void test()
{
int i = bar(3); }
This access can span multiple nesting levels:
int bar(int a)
{
int c = 3;
int foo(int b)
{
int abc()
{
return c; }
return b + c + abc();
}
return foo(3);
}
Static nested functions cannot access any stack variables of
any lexically enclosing function, but can access static variables.
This is analogous to how static member functions behave.
int bar(int a)
{
int c;
static int d;
static int foo(int b)
{
b = d; b = c; return b + 1;
}
return foo(a);
}
Functions can be nested within member functions:
struct Foo
{
int a;
int bar()
{
int c;
int foo()
{
return c + a;
}
return 0;
}
}
Nested functions always have the D function linkage type.
Unlike module level declarations, declarations within function
scope are processed in order. This means that two nested functions
cannot mutually call each other:
void test()
{
void foo() { bar(); } void bar() { foo(); } }
There are several workarounds for this limitation:
- Declare the functions to be static members of a nested struct:
void test()
{
static struct S
{
static void foo() { bar(); } static void bar() { foo(); } }
S.foo(); }
Declare one or more of the functions to be function templates
even if they take no specific template arguments:
void test()
{
void foo()() { bar(); } void bar() { foo(); } }
Declare the functions inside of a mixin template:
mixin template T()
{
void foo() { bar(); } void bar() { foo(); } }
void test()
{
mixin T!();
}
Use a delegate:
void test()
{
void delegate() fp;
void foo() { fp(); }
void bar() { foo(); }
fp = &bar;
}
Nested functions cannot be overloaded.
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(); }
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 function() fp1 = &abc;
int function() fp2 = &def;
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(); }
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(); return &b; }
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 = foo(&f.bar); }
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.
See FunctionLiterals.
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) { ... }
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:
void func(T)(T x)
{
writeln(x);
}
void main()
{
func!(int)(1); func(1); func("x"); func(1.0);
struct S {}
S s;
func(s); }
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.
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
- Reinterpretation of overlapped fields in a Union.
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); static assert(countTen(12) == 12);
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); writeln(j);
writeln(square(4)); writeln(eval!(square(5))); }
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.
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.
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 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 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 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.
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.
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();
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; obj.prop = 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;
X obj;
obj.foo(); obj.baz();
import b : boo = baz;
obj.boo(); }
class C
{
void mfoo(X);
static void sbar(X);
import b : ibaz = baz; void test()
{
X obj;
obj.ibaz(); }
}
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();
auto front = x; auto y = a.front(); }
class C
{
int[] arr;
int front()
{
return arr.front(); }
}