`@safe` by default. What about `@pure` and `immutable` by default?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Apr 18 08:18:05 UTC 2019


On Wednesday, April 17, 2019 1:25:24 AM MDT Eugene Wissner via Digitalmars-d 
wrote:
> On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote:
> > pure can be nice when it works, but really basic things like
> > I/O don't work if pure is used. You can get around that for
> > debugging with debug blocks, but if you have a bunch of code,
> > and it turns out that you need to do something in it that isn't
> > pure, you'd be screwed unless you go and mark a ton of code
> > with impure (or whatever the opposite of pure would be). It's
> > not like you can just opt-out in the middle like you can with
> > @safe by using @trusted to use @system code.
>
> I/O doesn't work in pure code, because I/O isn't pure. And if you
> want an impure statement in pure code, you can just cast purity
> away same as pureMalloc does it.

Of course, I/O isn't pure, and casting with pure is almost always the wrong
thing to do. It's like const. If you cast it away and mutate the value, then
the const is a lie, and the assumptions that the compiler makes are wrong,
which could result in wrong code. In the case of pure and I/O, casting could
easily mean that I/O isn't done which the code expects to be done, because
the compiler decided that a function didn't need to be called multiple
times, because it was pure, and the result would be the same. Similarly, if
the compiler is lied to about pure, that can really screw with immutable,
because the compiler is able to make assumptions based on the fact that the
code is pure and determine that some data has to be unique, because it could
not have possibly be passed into the function and thus had to have been
allocated within the function.

pureMalloc is one case where it's arguably okay to cast with regards to pure
- but there's been a lot of discussion about that, because it's incredibly
easy to screw up the compiler guarantees in the process. It works with the
GC, because of the extra control that the compiler has and the fact that
programmer isn't the one that has to deal with freeing the memory.

In general, if code is casting with regards to pure, it's almost certainly
wrong. Exceptions to that exist, but they're extremely rare, and they must
be done _very_ carefully to avoid running afoul of compiler guarantees.

> pure doesn't make any sense if it isn't default. Plenty of people
> for a valid reason don't care about the attributes. As soon as
> you have dependencies, you can't mark your own code as pure
> because you use some dependencies that may be 100% pure but
> aren't annotated as such. It  is possible to write pure-annotated
> code only if you have not-invented-here-syndrom like me and have
> no dependencies.
>
> Pure should either be default or be completely removed, it is
> absolutely useless as it is today.

If pure were the default, then you would have to turn it off on main on
pretty much every program ever written. That should tell you something. The
only programs which could avoid having main be pure would be those whose
only input is the arguments to main and whose only output was the return
value from main or a thrown exception that escapes main. And that's _very_
few programs.

For code to work as pure, it needs to be written with that in mind and then
cannot add stuff like I/O or caching later (caching could be added in _some_
cases when the cache is within a variable, but certainly, something like
Phobos' memoize wouldn't work). To try and force a program in general to be
pure is to be playing the same insane game that languages like Haskell play.
Sure, D's pure isn't quite the same thing (it really should be @noglobal at
this point, not pure), but the effect at the call site is the same, and many
of the same restrictions within a function still exist even if they're more
relaxed.

I'll grant you that it can be annoying to use pure when a library you depend
on doesn't use it properly, but by forcing it everywhere, the net result
will be that code all over the place will have to be marked with impure (or
@global) in order to work properly. And it's likely to be very common that
you'd then have to go through large portions of code and add @global all
over the place in order to add a piece of functionality that you need that
relates to I/O or caching or some other use case where you need to interact
with mutable data that isn't passed in as an argument.

pure works well when it is in smaller sections of code which were
specifically written to be pure, IMHO, it's a disaster if you try to mark
your entire program that way. It's just too easy to run into situations
where you can't have a piece of code be pure. It's a problem similar to
requiring const or immutable everywhere, only instead of restricting itself
to specific pieces of data, it invades the entire call stack. It does work
in some programs that are specifically written that way, but for a lot of
programs it won't - especially programs that are not written in a functional
manner. Trying to force an attribute like const, immutable, or pure as the
default is effectively trying to force D code to be written in a functional
manner instead of treating it as fully multiparadigm, and it's forcing a
paradigm that is very much not in line with the languages that D grew from
or with how D's standard libraries and common idioms currently work. D code
is typically more functional in nature than other languages that stem from
C/C++, but it's still very much an imperative, systems-level language.

- Jonathan M Davis





More information about the Digitalmars-d mailing list