diff --git a/changelog/copy-auto-ref.dd b/changelog/copy-auto-ref.dd new file mode 100644 index 00000000000..348ac4459a4 --- /dev/null +++ b/changelog/copy-auto-ref.dd @@ -0,0 +1,34 @@ +`std.algorithm.mutation.copy` Has Been Modified For `put`-style Output Ranges + +With this release, when the target output range in a call to +$(REF copy, std, algorithm, mutation) is any `put` defining output range (and it +doesn't have $(REF_ALTTEXT assignable elements, hasAssignableElements, std, range, primitives)), +the target is taken as `auto ref`. This new overload is also `void` returning +vs the other overloads which return the unfilled portion of the given range +or array. + +Before this release, $(REF copy, std, algorithm, mutation) took all +$(REF_ALTTEXT output ranges, isOutputRange, std, range, primitives) by value. +This leads to some issues when output ranges aren't designed around a reference +to heap memory, e.g. the hash ranges in $(MREF std,digest): + +------- +// v2.078 and earlier +import std.algorithm.mutation : copy; +import std.digest.digest : toHexString; +import std.digest.md : MD5; +import std.range : put; + +auto s = "Hello!\n"; +auto h1 = makeDigest!MD5; +auto h2 = makeDigest!MD5; + +put(h1, s); // takes h1 by ref +copy(s, h2); // takes h2 by value + +assert(h1.finish().toHexString == "E134CED312B3511D88943D57CCD70C83"); +// Different result because copy didn't properly insert the contents of s +assert(h2.finish().toHexString == "D41D8CD98F00B204E9800998ECF8427E"); +------- + +This issue no longer occurs in 2.079 and later as long as `target` is an lvalue. diff --git a/std/algorithm/mutation.d b/std/algorithm/mutation.d index 0d7936a1e33..4f145c3273a 100644 --- a/std/algorithm/mutation.d +++ b/std/algorithm/mutation.d @@ -351,18 +351,25 @@ private enum bool areCopyCompatibleArrays(T1, T2) = // copy /** -Copies the content of `source` into `target` and returns the -remaining (unfilled) part of `target`. +Copies the content of `source` into `target`. -Preconditions: `target` shall have enough room to accommodate +If `target` is an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) +which is a range with assignable elements, then `copy` returns the remaining +(unfilled) part of `target`. In this case, `target` must have enough room to accommodate the entirety of `source`. +If `target` is an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) +which defines a `put` method, than nothing is returned. In this case, it's generally +recommended to pass `target` as an lvalue, to avoid any issues with copying the output +data by value. + Params: source = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) - target = an output range + target = an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) Returns: - The unfilled part of target + The unfilled part of `target` if `target` is not a `put` defining output + range. `void` otherwise. See_Also: $(HTTP sgi.com/tech/stl/_copy.html, STL's _copy) @@ -399,7 +406,8 @@ if (areCopyCompatibleArrays!(SourceRange, TargetRange)) TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) if (!areCopyCompatibleArrays!(SourceRange, TargetRange) && isInputRange!SourceRange && - isOutputRange!(TargetRange, ElementType!SourceRange)) + isOutputRange!(TargetRange, ElementType!SourceRange) && + (isArray!TargetRange || hasAssignableElements!TargetRange)) { // Specialize for 2 random access ranges. // Typically 2 random access ranges are faster iterated by common @@ -422,6 +430,17 @@ if (!areCopyCompatibleArrays!(SourceRange, TargetRange) && } } +/// ditto +void copy(SourceRange, TargetRange)(SourceRange source, auto ref TargetRange target) +if (!areCopyCompatibleArrays!(SourceRange, TargetRange) && + isInputRange!SourceRange && + isOutputRange!(TargetRange, ElementType!SourceRange) && + !isArray!TargetRange && + !hasAssignableElements!TargetRange) +{ + put(target, source); +} + /// @safe unittest { diff --git a/std/digest/package.d b/std/digest/package.d index 3f3bc15f1ac..73e0b0447d8 100644 --- a/std/digest/package.d +++ b/std/digest/package.d @@ -265,7 +265,7 @@ version(ExampleDigest) auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); auto ctx = makeDigest!MD5(); - copy(oneMillionRange, &ctx); //Note: You must pass a pointer to copy! + oneMillionRange.copy(ctx); assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); } @@ -433,10 +433,10 @@ DigestType!Hash digest(Hash, Range)(auto ref Range range) if (!isArray!Range && isDigestibleRange!Range) { - import std.algorithm.mutation : copy; + import std.range.primitives : put; Hash hash; hash.start(); - copy(range, &hash); + put(hash, range); return hash.finish(); } @@ -634,7 +634,7 @@ interface Digest auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); auto ctx = new MD5Digest(); - copy(oneMillionRange, ctx); + oneMillionRange.copy(ctx); assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); } diff --git a/std/stdio.d b/std/stdio.d index 1df94f7a45e..7d0b576daf7 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -15,7 +15,6 @@ module std.stdio; import core.stdc.stddef; // wchar_t public import core.stdc.stdio; -import std.algorithm.mutation; // copy import std.meta; // allSatisfy import std.range.primitives; // ElementEncodingType, empty, front, // isBidirectionalRange, isInputRange, put @@ -3143,7 +3142,7 @@ void main() @system unittest { - import std.algorithm.mutation : reverse; + import std.algorithm.mutation : copy, reverse; import std.exception : collectException; static import std.file; import std.range : only, retro; @@ -4572,16 +4571,17 @@ private struct ChunksImpl /** Writes an array or range to a file. -Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). +Shorthand for `put(data, File(fileName, "wb").lockingBinaryWriter)`. Similar to $(REF write, std,file), strings are written as-is, rather than encoded according to the $(D File)'s $(HTTP en.cppreference.com/w/c/io#Narrow_and_wide_orientation, orientation). */ void toFile(T)(T data, string fileName) -if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) +if (is(typeof({ auto w = stdout.lockingBinaryWriter; put(w, data); }))) { - copy(data, File(fileName, "wb").lockingBinaryWriter); + auto w = File(fileName, "wb").lockingBinaryWriter; + put(w, data); } @system unittest