Command–query separation principle [re: @mustuse as a function attribute]

H. S. Teoh hsteoh at qfbox.info
Wed Oct 19 17:00:39 UTC 2022


On Wed, Oct 19, 2022 at 04:31:59PM +0000, mw via Digitalmars-d wrote:
[...]
> class AbstractPaths {
>   AbstractPaths remove (int I) {...}
> }
> 
> 
> class Paths : AbstractPaths {
>   @mustuse
>   AbstractPaths remove (int I) {...}
> }
> 
> 
> AbstractPaths absPaths = new Paths();
> 
> absPaths.remove(i);  // shall @mustuse be enforced here on absPaths?

No, because it is not enforceable.  Consider:

	class MyPaths : AbstractPaths {
		// N.B.: no @mustuse
		override AbstractPaths remove (int I) {...}
	}

	AbstractPaths absPaths = new MyPaths;
	absPaths.remove(i); // shall @mustuse be enforced?

That is, given an instance of AbstractPaths, *you cannot tell* whether
@mustuse should be enforced or not. If it's an instance of Paths, then
it must be enforced, but if it's an instance of MyPaths, then it must
not be.  Conclusion: we cannot enforce @mustuse in the base class
AbstractPaths.


> Let's step back, and ask why we want to introduce @mustuse in the
> beginning?  The purpose of the annotation is to help programmers do
> not discard important return values accidentally as what the OP's
> author has experienced disaster.
> 
> Then the answer is very clear: we do want @mustuse be enforced on the
> absPaths.remove(i) call in the above example!

No, the correct answer is that the base class method must also be
attributed with @mustuse.  It should be illegal to have a non- at mustuse
base class method overridden by a @mustuse derived class method.

Why?  Consider the following scenarios:

1) Base class has @mustuse, derived class does not.  Then the base
class's @mustuse can be circumvented by a derived class that omits the
attribute, defeating the purpose of @mustuse in the base class.

2) Base class does not have @mustuse, but derived class does.  Then
instances of the derived class, cast to the base class references,
allow bypassing @mustuse in the derived class.

If indeed @mustuse's purpose is to prevent accidentally discarding
important return values, i.e., something marked @mustuse must *never* be
silently dropped, then neither (1) nor (2) is acceptable.

Conclusion: if the base class method has @mustuse, then so must the
derived class method. If the base class method does not have @mustuse,
then the derived class method cannot have it either.


[...]
> And as a consequence, all the AbstractPaths' derived classes'
> remove(i) method now must have this annotation ( injected by the D
> compiler, the programmer does not have to manually add it in all the
> places).

This is not enforceable, since the base class and derived class could be
in two far-flung files, and due to incremental compilation the compiler
cannot enforce @mustuse in an unmarked method in a class that might have
some distant relative in the inheritance tree that has @mustuse.

The only way this can be done is to make it a compile error for a base
class method and a derived class method to have a mismatch in the
@mustuse attribute.


> That is why I say:
> 
> if a method is marked as @mustuse anywhere in the inheritance tree,
> that method in all the class levels in the whole inheritance tree
> became @mustuse!

This is correct.  But it cannot be implicit; if you attribute a method
@mustuse somewhere in the inheritance tree, then you must also write
@mustuse in every other class in the hierarchy. Otherwise it's not
enforceable.


T

-- 
Winners never quit, quitters never win. But those who never quit AND never win are idiots.


More information about the Digitalmars-d mailing list