www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - DConf '22: No-Allocated 0-terminated path strings

reply cc <cc nevernet.com> writes:
Catching up on the DConf '22 videos, really enjoyed the tricks 
Walter presents here for no-allocation strings:

[DConf '22: Strawberries and Cream aka Delightful Emergent 
Properties of D -- Walter 
Bright](https://www.youtube.com/watch?v=iuP-AWUyjp8)

In the Q&A segment, the first question asked whether the filename 
in the path example needs memory allocation to be passed to C, 
and is told it does, however the two methods presented can be 
easily combined to provide no-allocation 0-terminated strings 
from `chain()`ed paths by passing an `InputRange` rather than a 
`const(char)` to `toCStringThen`:

```d
//version=AllowMalloc;
auto toCStringThen(alias dg, Range)(Range src) /*nothrow*/ if 
(isInputRange!Range && !isInfinite!Range) {
	const len = src.walkLength;
	char[512] small = void;
	version(AllowMalloc) {
		import dmd.common.string : SmallBuffer;
		auto sb = SmallBuffer!char(len + 1, small[]);
		scope ptr = sb[];
	} else {
		enforce(len < small.length, format!"C string buffer overflow 
(%s >= %s)"(len, small.length));
		scope ptr = small[];
	}
	size_t i = 0;
	foreach (char c; src)
		ptr[i++] = c;
	ptr[len] = '\0';
	return dg(ptr);
}
void main() {
	string path = "include/";
	string name = "file";
	string ext = ".ext";

	auto filename = chain(path, name, ext);
	filename.writeln;
	filename.byChar.toCStringThen!(
		(str) => printf("printf: {%s}\n", str.ptr)
	);
}
```
May need to be cleaned up for character types and needs to 
iterate twice if allocations are going to be allowed, and 
walkLength/chain()'s range functions aren't nothrow as far as I 
can tell.  But otherwise thought this was kind of neat, just 
posting it here in case anyone else finds it handy.
Oct 21 2022
parent reply ag0aep6g <anonymous example.com> writes:
On Friday, 21 October 2022 at 13:49:01 UTC, cc wrote:
 ```d
 //version=AllowMalloc;
 auto toCStringThen(alias dg, Range)(Range src) /*nothrow*/ if 
 (isInputRange!Range && !isInfinite!Range) {
 ```
[...]
 May need to be cleaned up for character types and needs to 
 iterate twice if allocations are going to be allowed
Nitpick: You cannot iterate a true input range twice. You need a forward range for that.
Oct 21 2022
parent Krzysztof =?UTF-8?B?SmFqZcWbbmljYQ==?= <krzysztof.jajesnica gmail.com> writes:
On Friday, 21 October 2022 at 14:34:47 UTC, ag0aep6g wrote:
 Nitpick: You cannot iterate a true input range twice. You need 
 a forward range for that.
NitpickĀ²: you don't actually need to iterate the range twice ```d //version=AllowMalloc; auto toCStringThen(alias dg, Range)(Range src) /*nothrow*/ if (isInputRange!Range && !isInfinite!Range) { char[10] small = void; size_t i = 0; while(!src.empty && i < small.length) { small[i++] = src.front; src.popFront; } if(i == small.length) { version(AllowMalloc) { import std.container.array; Array!char large = small[]; large ~= src; large ~= '\0'; return dg(large.data); } else { throw new Exception( "C string buffer overflow (%s >= %s)" .format(i+src.walkLength, small.length-1) ); } } else { small[i] = '\0'; return dg(small[0..i]); } } ``` Unfortunately this version does multiple allocations if contents of the range do not fit into the small buffer, but you could avoid that by detecting if `src` is a forward range/defines `.length` and doing `large.reserve` or something similar.
Oct 21 2022