TDPL, p. 167:
as long as the mutable state in a function is entirely transitory (i.e., allocated on the stack) and private (i.e., not passed along by reference to functions that may taint it), then the function can be considered pure.
import std.stdio : writeln;
struct M{
int[4] _data;
pure ref int opIndex(size_t i){ return _data[i]; }
}
pure M foo(ref M m){
m[0] = 1234;
return m;
}
void main(){
M m1 = M([7, 7, 7, 7]);
writeln(m1);
foo(m1);
writeln(m1);
}
// output:
// M([7, 7, 7, 7])
// M([1234, 7, 7, 7])
The mutable state is transitory because it’s on the stack, correct? But it’s not private. So how is foo() allowed to modify m1?
purehas been expanded a bit since the release of TDPL, sincepureas TDPL describes turns out to be far too restrictive to be useful beyond simple math functions and the like. You can look at the online documentation for the current definition, but it essentially comes down to this:purefunctions cannot access any module-level or static variables which can be mutated during the course of the program (they must beconstvalue types orimmutableto be accessed from apurefunction).purefunctions cannot call any functions which are notpure.purefunctions cannot perform I/O.That’s it. There are no other restrictions. However, there are additional restrictions required if a
purefunction is going to be optimized such that it only gets called one time even if it’s used multiple times within a statement. Namely:immutableor implicitly convertible toimmutable.In theory that could be expanded to requiring that the function’s arguments must be
immutableor implicitly convertible toimmutable(so that a function withconstparameters could be optimized when it’s givenimmutablearguments), but that’s not currently the case.Such
purefunctions are sometimes referred to as “strongly”pure, whereas those which cannot be optimized would be referred to as “weakly”pure. TDPL describes stronglypurefunctions. Weaklypurefunctions were added in order to makepuremore generally usable.While weakly
purefunctions can alter their arguments, they cannot alter the global state, so when they’re called by stronglypurefunctions (which can’t alter their arguments), the guarantee that the stronglypurefunction’s return value will always be the same for the same arguments still holds. Essentially, because the weaklypurefunctions cannot mutate global state, they’re part of the private state of the stronglypurefunction that they’re called from. So, it’s very much in line with what Andrei describes in section 5.11.1.1pureis aspureDoes in TDPL, except that the private state of the function has been expanded to allow functions which can alter its private state without altering global state.Another major thing of note which has been added since TDPL with regards to
pureis function attribute inference.pure,nothrow, and@safeare inferred for templated functions (though not for normal functions). So, if a templated function can bepure, now it ispure. Its purity depends on what it’s instantiated with. So, it becomes possible to usepurewith templated functions, whereas before, you usually couldn’t, because if you made itpure, it wouldn’t work with an impure function. But if you didn’t make itpure, then you couldn’t use it with apurefunction, so it was a major problem forpure. Fortunately, attribute inference fixes that now though. As long as a templated function follows the rules listed above when it’s instantiated, then it’s consideredpure.