Why is `opequals` for objects not `@safe` by default?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Apr 5 05:56:05 UTC 2024


On Thursday, April 4, 2024 11:17:07 PM MDT Liam McGillivray via Digitalmars-d 
wrote:
> I am rather new to D, and have just began using the `@safe` and
> `@nogc` attributes. I have just begun using FLUID, a GUI library
> that requires safe delegates to be passed to it's objects.
>
> While writing in `@safe` mode has mostly been not very difficult,
> and I usually have a sense of why the compiler rejects what it
> rejects, I was quite surprised when the compiler rejected an
> `opEquals` between two class objects. This is the default
> `opEquals` function which from my understanding simply compares
> the two memory addresses.
>
> I don't see why this would ever be considered unsafe, even if the
> class isn't marked as `@safe`. Is doing this operation with a
> `@safe` class really any more or less risky than doing it with a
> `@system` class?
>
> Is this just an oversight, or was this a deliberate choice?

The functions on Object don't have attributes, because they existed before
the attributes were added to the language. In addition to that, as soon as
you put attributes on the base class function, you're putting restrictions
on the derived classes, and we really don't want to force a particular set
of attributes on every D class in existence.

Ideally, Object wouldn't have functions like opEquals, and they'd only be
added by derived classes so that programs could put whatever attributes on
them make the most sense, but that's a breaking change, so it hasn't
happened.

However, derived classes _can_ put more restrictive attributes on the
functions when they override them, since that's adding extra restrictions
rather than loosening them, so you can make opEquals on your derived class
@safe.

Also, when you use == with class references, it doesn't directly call
opEquals. Rather, it calls the free function, opEquals, from object.d which
does some additional checks (like whether the two references are the same or
whether they're null) before calling the member function, opEquals, on the
references (and it won't call the member function if it's not necessary).
And when it calls the member function on the references, if they're the same
class, it'll call whatever opEquals the derived class has rather than the
one on Object, so overloads such as those that take something other than
Object can be called, giving you more control over what opEquals looks like
on your class so long as you're comparing references of the same type. Of
course, if the class types don't match, then the Object version will be
called instead, but in that case, it'll also make sure that both
lhs.opEquals(rhs) and rhs.opEquals(lhs) are true, which fixes some subtle
bugs that you can get when comparing base classes and derived classes.

In any case, yes, we'd like to improve the situation with Object, but it's
very difficult to do so in a way that doesn't break existing code, which is
why we haven't been able to fix some of its issues as well as we'd like.

- Jonathan M Davis





More information about the Digitalmars-d mailing list