Thread overview
Limits of implicit conversion of class arrays
March 23

Is there a reason why

class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}

fails as

[1]: cannot implicitly convert expression ds of type Derived[] to Base[]
[2]: cast from Derived[] to Base[] not allowed in safe code

?

March 23

On Saturday, 23 March 2024 at 09:08:45 UTC, Per Nordlöw wrote:

>

Is there a reason why

class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}

fails as

[1]: cannot implicitly convert expression ds of type Derived[] to Base[]
[2]: cast from Derived[] to Base[] not allowed in safe code

?

The first and second is unsound (infamously allowed in Java). Once you cast the slice you can populate it with Derived2 objects that are not Derived, hence breaking type safety of the ds slice.


Dmitry Olshansky
CEO @ Glow labs
https://olshansky.me

March 23

On Saturday, 23 March 2024 at 11:04:04 UTC, Dmitry Olshansky wrote:

>

On Saturday, 23 March 2024 at 09:08:45 UTC, Per Nordlöw wrote:

>

Is there a reason why

class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}

fails as

[1]: cannot implicitly convert expression ds of type Derived[] to Base[]
[2]: cast from Derived[] to Base[] not allowed in safe code

?

The first and second is unsound (infamously allowed in Java). Once you cast the slice you can populate it with Derived2 objects that are not Derived, hence breaking type safety of the ds slice.


Dmitry Olshansky
CEO @ Glow labs
https://olshansky.me

Note that it works if the classes are const:

	const(Base)[] bs;
	const(Derived)[] ds;
	bs ~= ds; // pass
	bs = ds; // pass
	bs = cast(const(Base)[])ds; // pass

Exactly because you can't replace existing entries.

March 25

On Saturday, 23 March 2024 at 11:04:04 UTC, Dmitry Olshansky wrote:

>

The first and second is unsound (infamously allowed in Java).

In the general case, yes. But, do you see any errors with the code

class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}
>

Once you cast the slice you can populate it with Derived2 objects that are not Derived, hence breaking type safety of the ds slice.

Again, in the general case, yes.

So what is different in this code example compared to the general case? Hint: this has overlaps with a missing compiler optimization in dmd (and many other statically typed languages) enabled by a specific kind of data flow analysis. Which one?

March 28

On Monday, 25 March 2024 at 07:16:35 UTC, Per Nordlöw wrote:

>

On Saturday, 23 March 2024 at 11:04:04 UTC, Dmitry Olshansky wrote:

>

The first and second is unsound (infamously allowed in Java).

In the general case, yes. But, do you see any errors with the code

class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}

Yes, it's unsafe, as you can replace an element of ds with something that has no relation to Derived.

> >

Once you cast the slice you can populate it with Derived2 objects that are not Derived, hence breaking type safety of the ds slice.

Again, in the general case, yes.

So what is different in this code example compared to the general case? Hint: this has overlaps with a missing compiler optimization in dmd (and many other statically typed languages) enabled by a specific kind of data flow analysis. Which one?

If there is a way to end up with a Derived reference to point at something that is not a Derived without a cast in system code, or even with a cast in safe code, then it is an error. It doesn't matter if you aren't actually doing it.

If you know you are not making that mistake, change it to system, and cast to inform the compiler that you "know what you are doing".

-Steve

March 31

On Thursday, 28 March 2024 at 01:53:52 UTC, Steven Schveighoffer wrote:

> >
class Base {}
class Derived : Base {}

@safe pure nothrow unittest {
	Base b;
	Derived d;
	b = d; // pass

	Base[] bs;
	Derived[] ds;
	bs ~= ds; // pass
	bs = ds; // fail [1], should pass
	bs = cast(Base[])ds; // fail [2], should pass
}

Yes, it's unsafe, as you can replace an element of ds with something that has no relation to Derived.

This is a suggested change that when applied will make the code unsafe yes. But the code in its current form is safe and the compiler could be extended to prove it.