Function argument that is a pointer to memory which the function is not allowed to modify, as in C const

Jonathan M Davis newsgroup.d at jmdavisprog.com
Wed Mar 14 23:01:49 UTC 2018


On Wednesday, March 14, 2018 22:23:47 Cecil Ward via Digitalmars-d-learn 
wrote:
> say in C I have a function with a pointer argument
>      foo( const sometype_t * p )
>
> I have asked about this D nightmare before. Using the same
> pattern in D or the in argument qualifier as far as I can see the
> value of the pointer is then itself effectively locked made
> constant. Without dangerous and ugly casts you are stuck.
>
> q1. If you want a pointer to memory that is not to be modified
> then you can't walk the pointer through that memory.  So what are
> my options? I need a pointer that I can increment. (I could avoid
> the whole issue by using an index instead, but that seems to be
> giving in to madness.)

Once something is const, it can't be modified without violating the type
system, so if you need to mutate something, it can't be const. However, with
a pointer, you can have a mutable pointer to a const object (i.e.
tail-const), e.g.

const(sometype_t)* p;

and in that case, the pointer can be mutated while still not being able to
mutate what it points to. However, once the pointer itself is const, it
can't be mutated, and you'd be forced to copy the pointer to get a
tail-const pointer.

> It seems to me that this is the worst thing I have seen about D.
> Perhaps trying to make pointers unusable is a surreptious
> strategt]y for encouraging designers to phase them out. Making
> code unsafe just to get out of this nightmare (by casting or
> giving up and dropping important const protection) is not the way.

Code with pointers is @system where the compiler cannot guarantee that the
operation is memory safe. Given the nature of pointers, that means that
stuff like pointer arithmetic can't be treated as @safe. It works just fine,
but it's then up to the programmer to verify the @safety of what's going on.
D is only trying to discourage the use of pointers in the sense that any
time that it's trying to proved memory safety, it tends to have to restrict
code thoroughly enough that it can't do much with pointers. DIP 1000 will
improve that, since it improves scope sufficiently that a lot more code
using pointers will be able to be @safe, but fundamentally, pointers are
sufficiently unrestricted that they quickly can't be proven to be memory
safe. So, using them isn't forbidden, but their use does end up being
restricted in any code that's trying to guarantee memory safety. However,
there's no requirement that @safe be used, and you can use pointers pretty
much as freely in D as you can in C/C++.

As far as const goes, the way it works really has nothing to do with
pointers one way or the other beyond the fact that it allows for tail-const
so that pointers can be mutated while leaving the data const. const works
the way that it does in D, because it has to in order to not violate
immutable (since a pointer to const data could actually be pointing to
immutable data), and because Walter believes that const is pointless if it
doesn't provide strong compiler guarantees. So, unlike C++, D's const
provides strong compiler guarantees, but it does make it restrictive enough
that it's often unusable. The reasons behind it really have nothing to do
with trying to discourage pointers though.

> q2. If you want a pointer to modifiable memory but wish to ensure
> that the value of that address stays fixed, stays where it's put,
> then what on earth do you do. What are my options?
>
> Is there any way at all to campaign for a change to this
> craziness? I doubt this is a democracy. It's also rather more
> than a bit late.

Early on in D2, const actually supported the notion of head-const, but it
was deemed too complicated, and it really doesn't play well with immutable.
As such, while you can use parens with const to have tail-const (thereby
having a mutable pointer to const data), you can't have a const pointer to
mutable data. Once a part of a type is const, everything inside it is const.

However, you can wrap a pointer in a struct which allows you access to the
data that the pointer points to without allowing access to the pointer
itself, thereby effectively making the pointer read-only. It can't actually
be const, and it's extra work to create such a wrapper struct, but it's
quite possible if that's what you really want.

> q3. The in keyword seems to be mixed up concerning the
> distinction between modifiable arguments and modifiable memory.
> Is there any way of making in usable for the purposes of
> documenting the calling convention, showin which arguments are
> inputs only, which are outputs and which are modified -
> read-modified-returned?
>
> Apologies for my lack for my lack of familiarity with the
> possible ways out of this.

Originally in D2, in was a synonym for const scope (which, since scope has
mostly been unimplemented effectively was the same as const). IIRC, it was
an attempt to make porting D1 code to D2 easier, since D1 used in for
something similar. Folks have often used in in D2 mainly because they
thought that it went well conceptually with out, without really taking into
account what it really means - particularly that part about scope. So, it's
gotten used a fair bit when const could have and really should have been
used instead. If scope were properly implemented, and in then really meant
const scope as intended, a ton of code would break. However, now that scope
is finally being properly fleshed out and implemented as part of DIP 1000,
Walter Bright officially made in just a synonym for const and not const
scope, because he wanted to avoid the breakage that would be caused by
actually treating it as const scope now that scope actually does something
(when compiling with -dip1000 anyway).

So, really, you can ignore in entirely. And if/when you do see it, realize
that it's just const. There's nothing special about it. Using it means all
of the normal pros and cons that go with const.

> q4. If my understanding is correct, it seems difficult to create
> a non const copy of (an address that is fixed) either; that is,
> making a modifiable copy of an address, one which can be
> incremented, moved upwards starting from a locked base address.
> It seems that declaring a pointer argument with const or even
> using the keyword in triggers this problem, the latter being
> particularly nightmarish because I would want in to mean that
> that argument (the address, which is what I am declaring) is
> merely an input-only parameter to the routine, or alternatively a
> locked /fixed address value which stay s put, and nothing.

If you're just talking about a pointer, getting a mutable copy is trivial.

auto foo(const T* ptr)
{
    const(T)* local = ptr;
    ...
}

But in generaly, if you're using const, you have to be willing to not ever
mutate the data involved, and that often means that const simply is not a
good fit for a particular piece of code.

> I'm interested in the cleanest safest techniques for digging
> myself out of this while always preserving const correctness,
> preventing possibility of writing to memory and preventing evil
> type changes where pointers end up pointing to some different
> kind of objects because if evil casting. I really don't want to
> use casts that have to much power, where they could allow
> overrides to any kind of bugs in or even create a new bug,
> including cases when things break because of duplication of types
> so later changes of types cause a bug because kludge contain
> duplicate type specifiers that do not get updated.
>
> There probably is a tool somewhere to safely create a modifiable
> object based on a const object but I'm not sure where to look.
>
> Any wise guidance appreciated mucky.

Basically, you should never, ever cast away const. Casting it away is fine
so long as you can then guarantee that the data is never mutated, but
mutating data after casting away const is undefined behavior and can result
in subtle bugs. D does not have _any_ backdoors for getting around const.
When something is const in D, it's const. It can be mutated via a mutable
reference to the same data, but you can't legally get a mutable reference
from a const reference. So, if you have const, you're stuck. This provides
great guarantees (unlike C/C++) but often means that const is unusable in D.

If you're dealing with pointers or dynamic arrays, you can get a tail-const
pointer or slice to the data - e.g.

const(T*) ptr1;
const(T)* ptr2 = ptr1;

const(T[]) arr1;
const(T)[] arr2 = arr1;

and if the type is a value type, you can get a mutable copy, but once you're
dealing with const references or const data that you can't (or don't want
to) copy, you're stuck. Once something is const in D, it really is const.

That's why many D programmers do almost nothing with const. The guarantees
are great, but they're so restrictive as to make const borderline useless -
especially once user-defined types get involved. I would strongly advise you
to forget about "const-correctness" when writing D code. You can get it on
some level if you really work at it, but it's trivial to back yourself into
a corner where you're screwed because you have something that's const where
you need at least some portion of it to be mutable, and you can't have it.

I recently wrote up an article on the subject:

http://jmdavisprog.com/articles/why-const-sucks.html

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list