Thread overview
cast(immutable) vs extra variable
Sep 19, 2013
Daniel Davidson
Sep 19, 2013
Namespace
Sep 19, 2013
Daniel Davidson
Sep 19, 2013
Jonathan M Davis
Sep 19, 2013
bearophile
Sep 19, 2013
Daniel Davidson
Sep 22, 2013
Ali Çehreli
Sep 23, 2013
Daniel Davidson
Sep 23, 2013
Ali Çehreli
September 19, 2013
Multi-part question:

1) Why does the last line fail? If cast to immutable how is it different than z? I know it is related to the ref. I'm using ref because I think it is likely more efficient - so assume the char[16] were really char[1024].

2) If I got rid of the ref, how many copies of the data would be made? And without looking at assembly what is a good way to answer this question? I've tried to add this(this) to other Stuff type structures to print when they are being called, but that does not work because no logic can be in the default constructor.

3) Also, is storing immutable(STUFF) in a struct in the general case (as opposed to just this one) useful or silly?

Thanks
Dan

    import std.stdio;

    alias char[16] Stuff;

    struct T
    {
      immutable(Stuff) myData;
      this(ref immutable(Stuff) data) {
        myData = data;
      }
    }

    void main() {
      immutable(Stuff) iData = "1234567890123456";
      Stuff data = "1234567890123456";
      writeln(T(iData));
      auto iData2 = cast(immutable)data;
      writeln(T(iData2));
      // WHY DOES THIS FAIL WHEN T(iData2) works?
      // writeln(T(cast(immutable)data));
    }

September 19, 2013
On Thursday, 19 September 2013 at 16:47:13 UTC, Daniel Davidson wrote:
> Multi-part question:
>
> 1) Why does the last line fail? If cast to immutable how is it different than z? I know it is related to the ref. I'm using ref because I think it is likely more efficient - so assume the char[16] were really char[1024].
>
> 2) If I got rid of the ref, how many copies of the data would be made? And without looking at assembly what is a good way to answer this question? I've tried to add this(this) to other Stuff type structures to print when they are being called, but that does not work because no logic can be in the default constructor.
>
> 3) Also, is storing immutable(STUFF) in a struct in the general case (as opposed to just this one) useful or silly?
>
> Thanks
> Dan
>
>     import std.stdio;
>
>     alias char[16] Stuff;
>
>     struct T
>     {
>       immutable(Stuff) myData;
>       this(ref immutable(Stuff) data) {
>         myData = data;
>       }
>     }
>
>     void main() {
>       immutable(Stuff) iData = "1234567890123456";
>       Stuff data = "1234567890123456";
>       writeln(T(iData));
>       auto iData2 = cast(immutable)data;
>       writeln(T(iData2));
>       // WHY DOES THIS FAIL WHEN T(iData2) works?
>       // writeln(T(cast(immutable)data));
>     }

cast(immutable)data) is not an lvalue, it's a rvalue. ref accepts only lvalues.
September 19, 2013
On Thursday, 19 September 2013 at 16:50:32 UTC, Namespace wrote:

> cast(immutable)data) is not an lvalue, it's a rvalue. ref accepts only lvalues.

Thanks... about the other questions?
September 19, 2013
On Thursday, September 19, 2013 18:47:12 Daniel Davidson wrote:
> Multi-part question:
> 
> 1) Why does the last line fail? If cast to immutable how is it different than z? I know it is related to the ref. I'm using ref because I think it is likely more efficient - so assume the char[16] were really char[1024].

As Namespace pointed out, ref (including const ref) does not accept rvalues - only lvalues.

> 2) If I got rid of the ref, how many copies of the data would be made? And without looking at assembly what is a good way to answer this question? I've tried to add this(this) to other Stuff type structures to print when they are being called, but that does not work because no logic can be in the default constructor.

That would depend on what functions are being called and could change if the code is refactored. The compiler will do moves instead of copies whenever it can, so the situation is far better than with C++ (particularly pre-C++11), but how many copies will actually be needed depends on what the functions are doing and how many functions get called. Writing a postblit constructor would be the only way that I know to track it, though I'm not sure why the lack of default construction would be a problem, unless you're trying to name all of your objects and print out exactly how many each individual one is copied. But if you want to do that, then create a constructor that you pass the name, and don't use a default-initialized value.

> 3) Also, is storing immutable(STUFF) in a struct in the general
> case (as opposed to just this one) useful or silly?

As soon as you have a const or immutable member in a struct, you can't ever assign anything to it, even if all of its other members are mutable (you could assign the individual, mutable members but not the whole struct). That means stuff like if the struct is ever default-initialized, you can't even give it another value, which tends to mean that you can't use such a struct in places like arrays.

All in all, I think that it's pretty much always a bad idea to have a struct with const or immutable members. It's just begging for problems. Tail-const or tail-immutable is fine, because the members themselves aren't const or immutable (just what they refer to), but having them be directly const or immutable is a bad idea IMHO.

- Jonathan M Davis
September 19, 2013
Jonathan M Davis:

> All in all, I think that it's pretty much always a bad idea to have a struct
> with const or immutable members. It's just begging for problems. Tail-const or
> tail-immutable is fine, because the members themselves aren't const or
> immutable (just what they refer to), but having them be directly const or immutable is a bad idea IMHO.

The Rust language has avoided this problem, you can copy struct instances with const fields. But you can't update their const fields.

Bye,
bearophile
September 19, 2013
On Thursday, 19 September 2013 at 20:05:34 UTC, Jonathan M Davis wrote:
> As soon as you have a const or immutable member in a struct, you can't ever
> assign anything to it, even if all of its other members are mutable (you could
> assign the individual, mutable members but not the whole struct). That means
> stuff like if the struct is ever default-initialized, you can't even give it
> another value, which tends to mean that you can't use such a struct in places
> like arrays.
>

Ok. I think you can use in immutable(T)[], just not T[].

> All in all, I think that it's pretty much always a bad idea to have a struct
> with const or immutable members. It's just begging for problems. Tail-const or
> tail-immutable is fine, because the members themselves aren't const or
> immutable (just what they refer to), but having them be directly const or
> immutable is a bad idea IMHO.
>

I would like to understand the tail-const/immutable argument
better. Per this advice, it is ok to keep my members
tail-const/immutable. But it is ok if those structs to have members that
are const/immutable. So, why is it ok for members of classes that I
hold but not for my members. In the face of const/immutable
*transitivity* how is it more beneficial?

Here is a setup: Data is coming in - say over the wire or from a
database. It is very rich data with nestings of primitives, lists and
string keyed associative arrays, recursively - think nested json. Once
a data object is read in it is passed off to classes that use the data
in read-only fashion.

So, for example, assume the root of the rich data is a Portfolio
instance. All members of Portfolio recursively are public since it is
really plain old data from one perspective and this makes using vibe
json serialization simple. Assume that a user of Portfolio data is an
Analyzer. This analyzer will need to do lots of complex operations
with the portfolio that it uses in readonly fashion. It may have many
mutable members for intermediate calculations.

Here is a mockup http://pastebin.com/nBLFDgv6 which is making use of
immutable(Portfolio) to ensure that (a) the analyzer does not modify
the data and (b) no other code can modify the data. To achieve this
there needs to be a point in time where the modifiable Portfolio is
done being read and made immutable. At this point it is cast to
immutable and henceforth only accessed that way.

Given this setup - what do you see as the trade-offs?

What can not be done or will be challenging in face of refactor?

What are some better alternatives/recommendations?

I am a fan of D but my struggles with const/immutable transitivity
feel endless and are wearing on me. I feel like having an ability to
say something will not change and not using that ability is like being
teased.


September 22, 2013
On 09/19/2013 03:07 PM, Daniel Davidson wrote:

> Here is a setup: Data is coming in - say over the wire or from a
> database. It is very rich data with nestings of primitives, lists and
> string keyed associative arrays, recursively - think nested json. Once
> a data object is read in it is passed off to classes that use the data
> in read-only fashion.

And there are two types of read-only data:

const: A promise to not mutate

immutable: A requirement that nobody mutates either.

> So, for example, assume the root of the rich data is a Portfolio
> instance. All members of Portfolio recursively are public since it is
> really plain old data from one perspective and this makes using vibe
> json serialization simple. Assume that a user of Portfolio data is an
> Analyzer. This analyzer will need to do lots of complex operations
> with the portfolio that it uses in readonly fashion.

Yes, that sounds like immutable(Portfolio), as the Analyzer would not be happy if the data could mutate.

> It may have many
> mutable members for intermediate calculations.
>
> Here is a mockup http://pastebin.com/nBLFDgv6 which is making use of
> immutable(Portfolio) to ensure that (a) the analyzer does not modify
> the data and (b) no other code can modify the data. To achieve this
> there needs to be a point in time where the modifiable Portfolio is
> done being read and made immutable. At this point it is cast to
> immutable and henceforth only accessed that way.

I have added a couple of comments to you program:

import std.stdio;

struct Portfolio {
    double[string] data;
    // ...
}

struct HedgeRecommendation {
    string recommendation;
    this(int) {
        recommendation = "Sell, the market is rigged";
    }
}

struct Analyzer {
    // [Ali] Since you are worried about cost of copying, why not make this a
    //       pointer as well?
    immutable(Portfolio)* portfolio;
    this(ref immutable(Portfolio) portfolio) {
        // [Ali] Otherwise, this assignment would copy as well.
        this.portfolio = &portfolio;
    }

    HedgeRecommendation createHedge() {
        auto result = HedgeRecommendation(1);
        //...
        return result;
    }
}

// [Ali] If possible, make this function pure so that its return value can
//       automatically be casted to immutable.
Portfolio readPortfolio(string id) pure {
    // read portfolio from database
    return Portfolio([ "IBM" : 100.0, "SPY" : 300.0 ]);
}

HedgeRecommendation getHedgeRecommendation(string portfolioId) {
    // [Ali] No explicit cast needed because of that 'pure'.
    immutable(Portfolio) portfolio = readPortfolio(portfolioId);
    auto analyzer = Analyzer(portfolio);
    return analyzer.createHedge();
}

void main() {
    writeln(getHedgeRecommendation("1234"));
}

> Given this setup - what do you see as the trade-offs?
>
> What can not be done or will be challenging in face of refactor?
>
> What are some better alternatives/recommendations?
>
> I am a fan of D but my struggles with const/immutable transitivity
> feel endless and are wearing on me. I feel like having an ability to
> say something will not change and not using that ability is like being
> teased.

You are not alone. I tried to answer some of these questions in my DConf 2013 talk. I think I have only scratched the surface:

  http://dconf.org/talks/cehreli.html

Ali

September 23, 2013
On Sunday, 22 September 2013 at 17:26:01 UTC, Ali Çehreli wrote:
> You are not alone. I tried to answer some of these questions in my DConf 2013 talk. I think I have only scratched the surface:
>
>   http://dconf.org/talks/cehreli.html
>
> Ali

Ali, thank you for providing great feedback and suggestions. I've stepped away from D for several months and just getting back into it. I will try to out the use of pure when returning data that is to be passed into immutable contexts.

Regarding, "the why not make this a pointer as well?" for immutable(Portfolio)* portfolio - that might be good to consider. So far I have not used pointers in D directly - so just to be sure, my use of a direct pointer ensures that even if my one pointer is the last remaining reference to the data, it will not be garbage collected?

I really enjoyed your talk the first time at D Conf. Now I'm enjoying it again online. Thanks.
September 23, 2013
On 09/23/2013 06:34 AM, Daniel Davidson wrote:

> I've stepped away from D for several months and just getting
> back into it.

> the first time at D Conf

Actually, I've been thinking about you recently. My apologies for forgetting your last name. That's why I couldn't recognize you. :(

> I will try to out the use of pure when returning data that is
> to be passed into immutable contexts.

I don't think there is anything wrong with marking as many functions as pure. Indeed, the compiler does that for lambdas anyway. I am not sure where things are regarding regular functions.

> just to be sure, my use of a direct pointer ensures that even
> if my one pointer is the last remaining reference to the data,
> it will not be garbage collected?

Correct.

Ali