Function attribute best practices

H. S. Teoh hsteoh at
Mon Sep 12 16:48:20 UTC 2022

On Mon, Sep 12, 2022 at 09:14:42AM -0700, Ali Çehreli via Digitalmars-d-learn wrote:
> struct Foo(R) {
>     R r;
>     int i;
>     bool empty() @nogc nothrow pure @safe scope {
>         return r.empty;
>     }
>     auto front() @nogc nothrow pure @safe scope {
>         return r.front;
>     }
>     auto popFront() @nogc nothrow pure @safe scope {
>         r.popFront();
>     }
> }
> What are best practices here?
> Is this accurate: Because Foo is a template, it should not put any
> attribute on member functions? Or only member functions that use a
> member that depends on a template parameter? And non-members that are
> templates?

IMO, attributes on members of template aggregates should be omitted,
*except* where you want to enforce that attribute on all instantiations.
E.g., if there is some kind of semantic requirement that a method should
not mutate the state no matter what, then you could put `const` on it.

Otherwise, I'd say let the compiler infer the actual attributes, and use
appropriately-crafted unittests to catch attribute violations in the
template code. The reason for this is to maximize generality:

1) If some user wants to use your code with their own data type, but
they needed it to be, e.g., impure for some reason, or throwing, then
your template should "gracefully degrade" rather than refuse to compile
(because instantiating Foo with a type whose .front is throwing, for
example, would be a compile error).  To do this, we must let the
compiler infer attributes as much as possible -- so for an instantiation
with a nothrow .front it would infer nothrow, for example. But for an
instantiation involving a throwing user type, the compiler would infer
.front as throwing.

2) If some user uses your code in nothrow code, then your template
should not introduce throwing behaviour which would fail to compile. For
this, you should use appropriately-attributed unittests to ensure that,
eg., when Foo is instantiated with a non-throwing type it does not
introduce something that throws.

> It is scary because Foo works just fine until it is used with impure
> code.

Exactly, this is issue (1) above.

> Is putting function attributes on unittest blocks for catching such
> issues?
> @nogc nothrow pure @safe
> unittest
> {
>     // ...
> }
> No, it isn't because unless my unittest code is impure, I can't catch
> my incorrect 'pure' etc. on my member functions.

Sure you can.  The `pure unittest` code obviously must itself be pure
(otherwise it wouldn't compile). If Foo introduces impure behaviour,
then the unittest, being pure, wouldn't be allowed to call Foo's impure
methods, which is what we want.  What's the problem?


Fact is stranger than fiction.

More information about the Digitalmars-d-learn mailing list