Is there a way to get a compile error when returning a temporary
from a function and then not assigning it to a variable or
passing it to a different function? E.g:
struct S {
int[] a;
void morph() {}
}
warnOnDiscard
S foo() {
return S([1,2,3]);
}
unittest {
auto a = foo(); // Fine, assigned to variable.
bar(foo()); // Fine, passed to other function.
foo(); // Error: return value is discarded.
foo().morph(); // Error: return value is discarded.
}
I know that pure functions give errors when their results are
discarded, but in this case foo() might not be pure.
--
Simen

Is there a way to get a compile error when returning a temporary
from a function and then not assigning it to a variable or
passing it to a different function? E.g:
struct S {
int[] a;
void morph() {}
}
warnOnDiscard
S foo() {
return S([1,2,3]);
}
unittest {
auto a = foo(); // Fine, assigned to variable.
bar(foo()); // Fine, passed to other function.
foo(); // Error: return value is discarded.
foo().morph(); // Error: return value is discarded.
}
I know that pure functions give errors when their results are
discarded, but in this case foo() might not be pure.

Well, the first problem is that you simply can't warn or give an error about
that if the function isn't pure, because if it isn't pure, then it could be
doing work, and it might be perfectly okay for the return value to be
ignored. So, while it might make sense for some functions, it wouldn't make
sense for others, and unless it never makes sense to ignore the return
value, the compiler can't reasonably warn about it.
Having an attribute that tells the compiler that it's not okay to ignore the
return value for that specific function could solve that problem, because it
would give the programmer a way to tell the compiler that that particular
function is not doing work such that it's reasonable to ignore the return
value, but that brings up the second problem which is that no such attribute
exists and that there is no way to create attributes to tell the compiler to
warn about something. UDAs are simply there to be used by code introspection
done by user code. The compiler itself doesn't do anything interesting with
them. So, in order to get something like that, a DIP would be required.
IIRC, the Weka guys wanted to be able to have attributes tell the compiler
stuff so that it could yell at the programmer when appropriate, so I think
that there is some interest in this area, but I have no idea how such a
thing would be implemented. New built-in attributes that warn about specific
stuff could certainly be added, but in each case, there would have to be a
solid enough argument as to why it was worth adding that further
complication to the language. I have no idea what the chances of being
accepted would be for a DIP specifically for an attribute to warn if the
return value is ignored, but you can certainly create such a DIP if you feel
strongly enough about it and feel that you can argue it well.
- Jonathan M Davis

IIRC, the Weka guys wanted to be able to have attributes tell
the compiler stuff so that it could yell at the programmer when
appropriate, so I think that there is some interest in this
area, but I have no idea how such a thing would be implemented.
New built-in attributes that warn about specific stuff could
certainly be added, but in each case, there would have to be a
solid enough argument as to why it was worth adding that
further complication to the language. I have no idea what the
chances of being accepted would be for a DIP specifically for
an attribute to warn if the return value is ignored, but you
can certainly create such a DIP if you feel strongly enough
about it and feel that you can argue it well.

It's been on my mind for years, so it probably will happen
eventually. Can't promise this is the year, though.
--
Simen

Is there a way to get a compile error when returning a
temporary from a function and then not assigning it to a
variable or passing it to a different function? E.g:
struct S {
int[] a;
void morph() {}
}
warnOnDiscard
S foo() {
return S([1,2,3]);
}
unittest {
auto a = foo(); // Fine, assigned to variable.
bar(foo()); // Fine, passed to other function.
foo(); // Error: return value is discarded.
foo().morph(); // Error: return value is discarded.
}
I know that pure functions give errors when their results are
discarded, but in this case foo() might not be pure.
--
Simen

On top of what's said.
It would be very complex to implement properly.
For example the following should fail, but will be very hard for
the compiler to actually track.
struct S {
int[] a;
void morph() {}
}
warnOnDiscard
S foo() {
return S([1,2,3]);
}
void d(S s) {
}
unittest {
auto a = foo(); // This should fail, because a is
never used.
// This should also fail, because b is never used actually
used afterwards.
auto b = foo()
b.morph();
// This should fail, because d does not use c.
// A simply analysis of d will not work, you'll have to track
all passed values to d and whether they themselves should be
tracked with the attribute.
auto c = foo();
d(c);
// Same as above, should fail too since d doesn't use the
return value
d(foo());
}
The above is going to be very complex to implement in the
compiler.
Sure a simple check if it has just been assigned to a variable
would work fine, but in the reality people would most likely just
do something like this then:
auto unused = foo();
And there ... we "hacked" our way around the attribute.
So for it to work it has to track every single return value from
a function that implements the attribute and that is borderline
impossible to get correct.

unittest {
auto a = foo(); // This should fail, because a is
never used.

No it shouldn't. It is assigned to a variable, as the constraint
said.

// This should also fail, because b is never used actually
used afterwards.
auto b = foo()
b.morph();

No it shouldn't. It is assigned to a variable, as the constraint
said.

// This should fail, because d does not use c.
// A simply analysis of d will not work, you'll have to
track all passed values to d and whether they themselves should
be tracked with the attribute.
auto c = foo();
d(c);

No it shouldn't. It is assigned to a variable, as the constraint
said.

// Same as above, should fail too since d doesn't use the
return value
d(foo());

No it shouldn't. It is passed to a function, as the constraint
said.

}
The above is going to be very complex to implement in the
compiler.
Sure a simple check if it has just been assigned to a variable
would work fine, but in the reality people would most likely
just do something like this then:
auto unused = foo();
And there ... we "hacked" our way around the attribute.

Yes, that's the point. Now it's visible that you're discarding
the result.
The point isn't actually to make sure people keep the variable
around - that'd be silly - it's to inform the programmer that
potentially important information is discarded, and to stop the
programmer from calling mutating functions on a temporary return
value where it will have no effect.
For pure functions, this gives a warning:
auto fun() pure { return 3; }
unittest {
fun(); // calling without side effects discards return value
}
Does that in any way whatsoever stop me from assigning the return
value to a variable and then ignoring it? No. But doing so would
make me an idiot. The compiler suggests I cast it to void if I
really intended to discard the result. The reason for this is
cast(void) is relatively easy to grep for, and sticks out like a
sore thumb when looking through the code.

So for it to work it has to track every single return value
from a function that implements the attribute and that is
borderline impossible to get correct.

No it's not. It's *perfectly* impossible the way your scheme
works. What does this function do to its parameters?
extern(C) void foo(S s);
In other words, your idea of perfectly tracking how a value is
used cannot be implemented at all. Trying to equate that with
what I'm asking is disingenuous.
--
Simen