Should modifying private members be @system?

Jonathan M Davis newsgroup.d at
Fri Oct 4 12:37:26 UTC 2019

On Friday, October 4, 2019 5:55:11 AM MDT Dennis via Digitalmars-d wrote:
> Imagine I am making my own slice type:
> ```
> struct SmallSlice(T) {
>      private T* ptr = null;
>      private ushort _length = 0;
>      ubyte[6] extraSpace;
>      this(T[] slice) @safe {
>          ptr = &slice[0];
>          assert(slice.length <= ushort.max);
>          _length = cast(ushort) slice.length;
>      }
>      T opIndex(size_t i) @trusted {
>          return ptr[0.._length][i];
>      }
> }
> ```
> The use of @trusted seems appropriate here, right? I'm doing
> pointer slicing, but I know that the _length member is correct
> for ptr since it came from a slice. The struct couldn't have been
> void-initialized or overlapped in a union since that's not
> allowed in @safe code when there are pointer members. Using {
> initializers } is not allowed since I have a constructor, and the
> members are private so they can't be modified outside of my
> (thoroughly audited) module.
> Except that last thing isn't true:
> ```
> import std;
> void main() @safe {
>      int[4] arr = [10, 20, 30, 40];
>      auto slice = SmallSlice!int(arr[]);
>      __traits(getMember, slice, "_length") = 100; // change
> private member
>      writeln(slice[9]); // out of bounds memory access
> }
> ```
> __traits(getMember) enables one to bypass private, so the
> @trusted use was incorrect. This bypassing makes it really hard
> to write @trusted code relying on the integrity of the struct.
> How are you going to do @safe reference counting when the
> reference count can be tampered with from @safe code outside the
> control of the library writer?
> I'm tossing the idea that __traits(getMembers) should be @system
> when it's bypassing visibility constraints of the member. What do
> you think?

There isn't necessarily anything unsafe about accessing public member
variables. An @safe function isn't going to be doing anything @system
regardless of what's done to the member variables by other functions,
because if whether an operation is memory safe or not depends on the state
of a variable, then that operation is always considered @system, and
@trusted would have to be used to make it @safe.

However, if a member function is marked @trusted, then it's up to the
programmer to ensure that it's guaranteed to be @safe in spite of whatever
@system operations it's doing, and if that function relies on the member
variables being in a particular state, and that state cannot actually be
guaranteed, because the member variables are public and could have been
messed with, then it's inappropriate to mark the function as @trusted. It's
only appropriate to mark it as @trusted if the programmer can guarantee that
what it's doing is @safe even if the member variables were messed with.

So, in general, @safe isn't a problem with public member variables, but
@trusted is.

Though honestly, having public member variables is problematic in general.
It's okay for POD types, since they're just data, and in code that's not
publicly distributed, having public member variables isn't necessarily a
problem, because they can be made private with property functions if
necessary. However, because turning public member variables into property
functions breaks code, it's not the sort of thing that you can do when you
don't control all of the code that uses it. As such, I think that it's
rarely a good idea to use public member variables in public libraries,
though in private applications, it's not as big a deal, because then the
programmer or company control all of the code and can fix any issues that
pop up when APIs are changed in a way that breaks code.

- Jonathan M Davis

More information about the Digitalmars-d mailing list