Immutable member functions and private members
simendsjo
simendsjo at gmail.com
Wed Aug 3 03:20:19 PDT 2011
On 03.08.2011 12:01, Jonathan M Davis wrote:
> On Wednesday 03 August 2011 11:44:27 simendsjo wrote:
>> On 03.08.2011 10:52, Jonathan M Davis wrote:
>>> On Wednesday 03 August 2011 10:37:58 simendsjo wrote:
>>>> I have a struct with a private member that is only ever accessed
>>>> through
>>>> a single property method - even from within the struct.
>>>> As this property fills the value on the first access, it cannot be
>>>> immutable, and as such, none of the many methods accessing this
>>>> property
>>>> can be immutable methods.
>>>>
>>>> This is according to specification, but I thought that since the
>>>> single
>>>> write to the property is done at one, and only one, access point, that
>>>> it would be safe?
>>>>
>>>> I could fill this value in the constructor, but it's a bit slow, so
>>>> I'd
>>>> rather do it only if needed.
>>>>
>>>> And is there any potential performance optimizations done by the
>>>> compiler, or is it "only" for safety?
>>>> Is there a way to hack around this, and more importantly, is it safe
>>>> to
>>>> do so, or will I open Pandora's box?
>>>>
>>>>
>>>> Small example:
>>>>
>>>> int len(const char[] c) {
>>>>
>>>> return c.length;
>>>>
>>>> }
>>>>
>>>> struct S {
>>>>
>>>> private immutable(char)[] _v;
>>>> @property immutable(char[]) v() { // Cannot be immutable
>>>> method
>>>>
>>>> if(!_v)
>>>>
>>>> _v = "init"; /* or from external function
>>>> */
>>>>
>>>> return _v;
>>>>
>>>> }
>>>>
>>>> @property int a() { // and so this cannot be immutable
>>>> method
>>>>
>>>> return len(v); /* notice the property function v
>>>> that might
>>>>
>>>> modify _v */
>>>>
>>>> }
>>>>
>>>> }
>>>>
>>>> void main() {
>>>>
>>>> S s;
>>>> s.a;
>>>>
>>>> }
>>>
>>> You're basically looking for logical const - albeit a subset which would
>>> be much easier to implement were we to implement it (that is, a lazy
>>> initialized const or immutable member variable). D has no support for
>>> logical const. Even worse, you're looking for logical immutable (which
>>> makes no sense at all beyond perhaps lazy initialization and probably
>>> doesn't even make sense there).
>>>
>>> The thing is that immutable methods are pointless unless you make the
>>> struct immutable (if you want to be able to call them with both a
>>> mutable and immutable instance of the struct, then you need the
>>> functions to be const, not immutable). And if you make the struct
>>> immutable, the compiler is free to put it in read-only memory if it so
>>> chooses, at which point setting _anything_ in the struct after the
>>> constructor has run is likely to blow up. So, even if you can get
>>> around the issue via casts and get both lazy initialization and
>>> immutable methods, there's a good chance that it'll blow up at some
>>> point (as in segfault or worse).
>>>
>>> If you were trying to do this with const, you might get away with it
>>> (though you'd be stepping outside of the type lsystem by casting away
>>> const and then altering anything - it's undefined behavior). But with
>>> immutable, there's no way that this is a good idea.
>>>
>>> Lazy initialization with const or immutable member variables just is
>>> _not_ a good idea in D. D provides no type-safe way to do this. You
>>> must break the type system by casting away const or immutable to even
>>> attempt it. Convievably, in the case of const, the language could be
>>> extended to allow for lazy initialization of member variables, but
>>> there's no way that it could do that with immutable (because the
>>> variable could conceivably be put in read- only memory), and even if it
>>> were done, it would likely have to be a D3 feature. Syntactically, it
>>> would probably be something like this:
>>>
>>> lazy int v = initFunc();
>>>
>>> and then when v was first accessed, initFunc would be called and v set
>>> to that value. But that could be ugly and inefficient to implement even
>>> if it's theoretically possible, so I wouldn't bet on anything like that
>>> making it into the language. Regardless, it wouldn't be until D3. For
>>> now, D doesn't support any kind of logical const.
>>>
>>> http://stackoverflow.com/questions/4219600/logical-const-in-d
>>>
>>> - Jonathan M Davis
>>
>> Thanks!
>>
>> I'm not really sure the compiler could put my struct in ROM.
>> My lazy parameter is immutable(char)[], so the compiler should see that
>> I have a non-immutable reference.
>>
>> The entire struct is immutable without this lazy variable though.
>> It only has two handles for passing to external functions.
>> I really would like to always use it only as immutable s = S(123). It
>> makes no sense for it to be mutable at all.
>>
>> Below is an exact example of what I want to do.
>> If I move the handle2 calculation to the ctor and use const methods, I
>> still cannot call the methods using a const variable though.. Bug?
>> const s = S(100);
>> s.a; // function t.S.a () immutable is not callable using argument type
>> It says immutable when it should say const..?
>>
>> immutable s = S(100);
>> s.a; // works on both const and immutable
>
> If a variable is const, you should only be able to call const functions on it.
> If it's immutable, you can call either const or immutable functions. If it's
> mutable, then you can call either const or non-const, non-immutable functions.
> If it's complaining about being unable to call a function on an immutable
> variable when the variable is const, then it's a bug.
>
>> ----
>>
>> import std.conv, std.exception;
>>
>> // external expensive function
>> extern(System) char[] getHandle2(const int handle) {
>> return to!(char[])(handle);
>> }
>>
>> // other external functions taking string handle instead of int
>> extern(System) int extFunc1(string handle2) {
>> return to!int(handle2);
>> }
>>
>> struct S {
>> immutable int handle;
>> private immutable(char)[] _handle2;
>>
>> this(int handle) {
>> this.handle = handle;
>> }
>>
>> @property immutable(char[]) handle2() {
>> if(!_handle2) {
>> auto buf = getHandle2(handle);
>> _handle2 = assumeUnique(buf);
>> }
>> return _handle2;
>> }
>>
>> @property int a() {
>> return extFunc1(handle2);
>> }
>>
>> // many more properties like this
>> }
>>
>> void main() {
>> auto s = S(100);
>> assert(s.a == 100);
>>
>> immutable s2 = S(100);
>> //assert(s2.a == 100); // oops, a not immutable
>> }
>
> You can't call a non-const, non-immutable function on an immutable variable. a
> must either be const or immutable to be callable here.
>
> In the general case, I'd advise against trying to have member variables of
> structs which are either const or immutable, because then you can't assign to
> them, which means that you can't use them in arrays and the like - only as
> local variables which are directly initialized. As long as the struct is
> capable of being immutable, you can then have immutable variables of that type
> if you want to, but you can then also stick it in arrays and the like if need
> be. But until http://d.puremagic.com/issues/show_bug.cgi?id=4867 is fixed,
> dealing with const structs which need a postblit doesn't work, so depending on
> what you're doing using const or immutable with structs won't necessarily
> work. It'll usually work, but you could run into trouble if a struct has any
> kind of indirection in it.
>
> In any case, you pretty much either have to completely initialize a struct in
> its constructor or you can't have its member functions be const or immutable,
> and there are definite issues with having const or immutable structs (both in
> terms of bugs in the current implementation and because anything using init is
> stuck with the init value). So, I'd be careful about trying to force a struct
> to always be const or immutable. It's generally doable, but there can be
> negative consequences to it.
>
> - Jonathan M Davis
Thanks for your detailed answers.
I'll stay away from trying to force it into immutable by casting :)
It doesn't give away any mutable references anyway, so it's immutable in
it's use even if the compiler doesn't know that.
More information about the Digitalmars-d-learn
mailing list