Thread overview
Possible 'pure' bug with int[] slice. Programming in D page 174
Oct 08
Kagamin
October 04

Did I discover a bug, or am I misunderstanding intentional behavior?
I would not expect a 'pure' function to modify a parameter.

void main()
{
	import std.stdio : writeln;

	int[] numbers = [5, 6, 7, 8, 9];
	writeln("numbers before: ", numbers);
	writeln("inner(numbers): ", inner(numbers));
	writeln("numbers after : ", numbers);
}

int[] inner(int[] slice) pure
{
	// This should not be pure.  I broke purity by mutating slice[0]
	// D compiler is not catching this!
	slice[0] = 500;

	if (slice.length)
	{
		--slice.length;
		if (slice.length)
		{
			slice = slice[1 .. $];
		}
	}
	return slice;
}

Output of console:

numbers before: [5, 6, 7, 8, 9]
inner(numbers): [6, 7, 8]
numbers after : [500, 6, 7, 8, 9]
October 04

On Saturday, 4 October 2025 at 10:56:09 UTC, Brother Bill wrote:

>

Did I discover a bug, or am I misunderstanding intentional behavior?
I would not expect a 'pure' function to modify a parameter.

It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.

October 04

On Saturday, 4 October 2025 at 12:14:06 UTC, Dmitry Olshansky wrote:

>

It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.

So if we don't want to allow mutating passing in parameters, add 'in' keyword to each parameter. Then it should be strongly cure. Is that correct?

int[] inner(in int[] slice) pure
October 04

On Saturday, 4 October 2025 at 12:40:43 UTC, Brother Bill wrote:

>

On Saturday, 4 October 2025 at 12:14:06 UTC, Dmitry Olshansky wrote:

>

It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.

So if we don't want to allow mutating passing in parameters, add 'in' keyword to each parameter. Then it should be strongly cure. Is that correct?

int[] inner(in int[] slice) pure

If you want to enforce (and also document) that an parameter is never changed by the function, you should use const. If I'm reading the spec correctly (https://dlang.org/spec/function.html#in-params), in always implies const, and with the -preview=in compiler switch, it also implies scope

October 04

On Saturday, 4 October 2025 at 12:40:43 UTC, Brother Bill wrote:

>

On Saturday, 4 October 2025 at 12:14:06 UTC, Dmitry Olshansky wrote:

>

It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.

So if we don't want to allow mutating passing in parameters, add 'in' keyword to each parameter. Then it should be strongly cure. Is that correct?

int[] inner(in int[] slice) pure

Yes that’s it.

October 05

On Saturday, 4 October 2025 at 12:40:43 UTC, Brother Bill wrote:

>

On Saturday, 4 October 2025 at 12:14:06 UTC, Dmitry Olshansky wrote:

>

It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.

So if we don't want to allow mutating passing in parameters, add 'in' keyword to each parameter. Then it should be strongly cure. Is that correct?

int[] inner(in int[] slice) pure

To give a little more context:

Traditionally, pure functions, and functional programming, do not allow mutation of parameters. Because that would disable functional optimizations (such as changing pureFunc(1) + pureFunc(1) into 2 * pureFunc(1).

But what happens inside a pure function? In D we write non-functional imperative code with mutable variables. What does it matter to the caller of pureFunc whether there are some mutations going on, as long as there are no observable side effects?

There are some nice abstractions to be had with mutating parameters, which could be used from both traditional pure functions, and impure functions.

For example, consider sorting. sort(arr) sorts an array in-place. But effectively, it has no side effects aside from that one.

So as long as the swapping that occurs during sort is not causing observable side effects, it can be considered "weakly pure".

This means it is possible to take the existing std.algorithm.sort function, and use it from a strongly pure function:

const(int[]) pureSort(const(int[]) input) pure {
   auto result = input.dup; // make a copy;
   result.sort(); // sort the copy;
   return result;
}

Such a function can be considered "strong" or traditionally pure, since there are no observable side effects (other than memory being allocated, but this is an accepted exception for functional languages).

The benefit here is, we can mark sort as a pure function, even though it modifies the parameter, and then we don't have to write a "special" implementation for sorting for pure functions. You just use the normal D ones.

-Steve

October 08

To be pedantic, strong purity requires immutable arguments, const are not enough:

void main()
{
	import std.stdio : writeln;

	int[] numbers = [5, 6, 7, 8, 9];
	writeln("numbers before: ", id(numbers));
	numbers[0]=1;
	writeln("numbers after : ", id(numbers));
}

const(int)[] id(const int[] p) pure
{
	return p;
}
October 08

On Wednesday, 8 October 2025 at 08:48:12 UTC, Kagamin wrote:

>

To be pedantic, strong purity requires immutable arguments, const are not enough:

void main()
{
	import std.stdio : writeln;

	int[] numbers = [5, 6, 7, 8, 9];
	writeln("numbers before: ", id(numbers));
	numbers[0]=1;
	writeln("numbers after : ", id(numbers));
}

const(int)[] id(const int[] p) pure
{
	return p;
}

Yes, thank you for the correction.

I got this confused with pure factory functions, where returning a mutable item from const parameters allows the compiler to assume the return value is unique.

-Steve

October 15
On Saturday, 4 October 2025 at 18:39:18 UTC, Alex Bryan wrote:
> On Saturday, 4 October 2025 at 12:40:43 UTC, Brother Bill wrote:
>> On Saturday, 4 October 2025 at 12:14:06 UTC, Dmitry Olshansky wrote:
>>> It’s weakly pure in that it could only mutate things that are passed in. Strongly pure would require no mutable pointers/slices/references.
>>> It may be misleading that it’s single keyword for both of these. The good news is that strongly pure functions can call weakly pure function and stay strongly pure.
>>
>> So if we don't want to allow mutating passing in parameters, add 'in' keyword to each parameter.  Then it should be strongly cure.  Is that correct?
>> ```
>> int[] inner(in int[] slice) pure
>> ```
>
> If you want to enforce (and also document) that an parameter is never changed by the function, you should use `const`. If I'm reading the spec correctly (https://dlang.org/spec/function.html#in-params), `in` always implies `const`, and with the -preview=in compiler switch, it also implies `scope`

Correct, and additionally `in` parameters might be passed by reference at the discretion of the compiler, essentially depending on if it’s cheap to copy.

The TL;DR for `in` is: Only use it if you understand what it does. Generally speaking, use `const`. With DIP1000, preview-`in` on a `@system` function can give you nasty memory corruption bugs because the implied `scope` might make the compiler allocate something on the stack when it actually escapes. On a `@safe` function, the compiler will check that you don’t escape `scope` parameters, but on a `@system` function, the compiler simply trusts your promise.