Jump to page: 1 2 3
Thread overview
Implicit conversion of concatenation result to immutable
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Ali Çehreli
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
H. S. Teoh
Apr 01, 2021
Ali Çehreli
Apr 01, 2021
Q. Schroll
Apr 01, 2021
H. S. Teoh
Apr 01, 2021
Ali Çehreli
Apr 01, 2021
H. S. Teoh
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Ali Çehreli
Apr 02, 2021
Per Nordlöw
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Per Nordlöw
Apr 01, 2021
Per Nordlöw
April 01, 2021

Can somebody explain the logic behind the compiler disallowing both line 3 and 4 in

const(char)[] x;
string y;
string z1 = x ~ y; // errors
string z2 = y ~ x; // errors

erroring as

Error: cannot implicitly convert expression `x ~ cast(const(char)[])y` of type `char[]` to `string`
Error: cannot implicitly convert expression `cast(const(char)[])y ~ x` of type `char[]` to `string

Has this something to do with the compiler being defensive about possible in-place appending or prepending to the arguments (in this case x and y) passed to the array concatenation expression?

For instance, could x ~ y return either

  • a back-extended slice x[0 .. x.length + y.length] with y appended at the back or
  • a front-extended slice y[-x.length .. y.length] with x prepended to the front

provided the GC has information about available free memory there?

This problem regularly crops up for me during assembling of strings passed as a string parameter for instance an exception constructor.

April 01, 2021
On 4/1/21 5:21 PM, Per Nordlöw wrote:

> Error: cannot implicitly convert expression `x ~ cast(const(char)[])y` of type `char[]` to `string`

That makes no sense. The compiler should allow that conversion. It can clearly prove that the result doesn't derive from the parameters.

I'm surprised it doesn't return const(char)[].

-Steve
April 01, 2021
On 4/1/21 2:41 PM, Steven Schveighoffer wrote:

> On 4/1/21 5:21 PM, Per Nordlöw wrote:
>
>> Error: cannot implicitly convert expression `x ~ cast(const(char)[])y`
>> of type `char[]` to `string`
>
> That makes no sense. The compiler should allow that conversion. It can
> clearly prove that the result doesn't derive from the parameters.
>
> I'm surprised it doesn't return const(char)[].
>
> -Steve

It should even return char[], no? Freshly copied data should belong to the programmer.

Ali


April 01, 2021

On Thursday, 1 April 2021 at 21:41:06 UTC, Steven Schveighoffer wrote:

>

I'm surprised it doesn't return const(char)[].

I think it returning char[] is sound.

What's unsound is that the compiler lacks knowledge of it being a unique slice (no aliasing).

The situation is analogous with

alias T = int;
const n = 42;
auto x = new T[n]; // no conversion
static assert(is(typeof(x) == T[]));
immutable(T)[] y = new T[n]; // implicit conversion to immutable allowed
static assert(is(typeof(y) == immutable(T)[]));

which is currently accepted by the compiler.

April 01, 2021

On Thursday, 1 April 2021 at 21:45:08 UTC, Ali Çehreli wrote:

>

It should even return char[], no? Freshly copied data should belong to the programmer.

Precisely.

April 01, 2021
On Thu, Apr 01, 2021 at 09:21:02PM +0000, Per Nordlöw via Digitalmars-d wrote:
> Can somebody explain the logic behind the compiler disallowing both line 3 and 4 in
> 
> ```d
> const(char)[] x;
> string y;
> string z1 = x ~ y; // errors
> string z2 = y ~ x; // errors
> ```
> 
> erroring as
> 
> ```
> Error: cannot implicitly convert expression `x ~ cast(const(char)[])y` of
> type `char[]` to `string`
> Error: cannot implicitly convert expression `cast(const(char)[])y ~ x` of
> type `char[]` to `string
> ````

It is illegal to implicitly convert const to immutable, because there may be a mutable alias to the data somewhere. If so, it will violate immutability. For example:

	char[] evil;
	const(char)[] x = evil;	// x now aliases evil
	string y = x;		// y is now aliases evil <----
	evil[0] = 'a';		// Oops, immutability violated

The implicit conversion on the line marked `<----` is the cause of the problem.

Now, when you append a string to a const(char)[], the compiler has to
promote `string` to `const(char)[]` first, so that the operands of ~
have the same type. And obviously, the result of concatenating two
const(char)[] must be const(char)[], since you don't know if one of them
may have mutable aliases somewhere else.  So the result must likewise be
const(char)[].

One may argue that appending in general will reallocate, and once reallocated it will be unique, and there safe to implicitly convert to immutable.  However, in general we cannot guarantee this, e.g., one of the strings could be empty and not reallocate at runtime, so it may continue to be aliased by some mutable reference somewhere else. So the result must be typed as const(char)[], along with the restriction that it cannot implicitly convert to immutable.


[...]
> This problem regularly crops up for me during assembling of strings passed as a string parameter for instance an exception constructor.

Just use .idup on the result.


T

-- 
What's a "hot crossed bun"? An angry rabbit.
April 01, 2021
On 4/1/21 2:59 PM, H. S. Teoh wrote:

> the result of concatenating two
> const(char)[] must be const(char)[], since you don't know if one of them
> may have mutable aliases somewhere else.  So the result must likewise be
> const(char)[].
>
> One may argue that appending in general will reallocate, and once
> reallocated it will be unique, and there safe to implicitly convert to
> immutable.  However, in general we cannot guarantee this

Yes, that's tricky for append because one of many slices does own the potential bytes after the array and will append elements in there. However, concatenation always makes a new array, right? I think the result can be char[] in that case.

Ali

April 01, 2021

On Thursday, 1 April 2021 at 21:21:02 UTC, Per Nordlöw wrote:

>

Can somebody explain the logic behind the compiler disallowing both line 3 and 4 in

I'm very intrigued by the fact the this issue hasn't been discussed more. It smells to me like this behaviour is somewhat by design...because I don't think it is at all difficult to fix.

During implicit conversion checking, just peek into the expression and see if it's CatExp and if so treat it as a unique reference and allow conversion from mutable non-mutable.

However, note that to solve this in the general case we have to involve data flow and escap e analysis. And have a tag associated with a variable that indicates whether it is aliased or not (on, yes, maybe).

Consider, for instance,

auto c = a ~ b;
e = d; // `d` aliased to `e`
f = foo(e); // `e` maybe aliased to `f`
immutable g = e; // allowed?
April 01, 2021

On Thursday, 1 April 2021 at 21:59:21 UTC, H. S. Teoh wrote:

>

Just use .idup on the result.

That creates an unnecessary GC allocation in cases such

string x;
const(char)[] y;
throw new Exception(x ~ y.idup)

which, imho, is very much worth considering avoiding the need for.

The implicit conversion could be special cased in the compiler to be allowed only on a CatExp being an r-value, naturally non-aliased.

As mentioned above this is analogous with conversion rules of new expressions.

April 01, 2021

On Thursday, 1 April 2021 at 22:17:08 UTC, Per Nordlöw wrote:

>

On Thursday, 1 April 2021 at 21:59:21 UTC, H. S. Teoh wrote:

>

Just use .idup on the result.

That creates an unnecessary GC allocation in cases such

string x;
const(char)[] y;
throw new Exception(x ~ y.idup)

The alternative

throw new Exception((x ~ y).idup)

, I now realized you referred to, is also unsound because x ~ y is already a unique freshly allocated unaliased slice that can be freely implicitly converted to immutable.

« First   ‹ Prev
1 2 3