www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why do immutable variables need reference counting?

reply norm <norm.rowtree gmail.com> writes:
Hi All,

I am clearly misunderstanding something fundamental, and probably 
obvious :D

Reading some of the discussions on __metadata I was wondering if 
someone could explain why a immutable reference counting type is 
needed. By definition a reference counter cannot be immutable, so 
what would be the use case that requires it? It cannot really be 
pure nor safe either because the ref goes out of scope and the 
allocation is freed. How is this immutable?


Thanks,
Norm
Apr 10 2022
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
immutable isn't tied to lifetime semantics.

It only says that this memory will never be modified by anyone during 
its lifetime.

Anyway, the real problem is with const. Both mutable and immutable 
become it automatically.
Apr 10 2022
next sibling parent reply norm <norm.rowtree gmail.com> writes:
On Sunday, 10 April 2022 at 23:19:47 UTC, rikki cattermole wrote:
 immutable isn't tied to lifetime semantics.

 It only says that this memory will never be modified by anyone 
 during its lifetime.
This is clearly where I am misunderstanding. In my mind immutable data means the data will not change and neither will the result of reading that data, ever. I don't get how you can have thread safety guarantees based on immutable if reading that data in a thread suddenly becomes undefined behaviour and could return anything. That isn't immutable then. Once instantiated immutable data persists for the remainder of the program. You may not have access if the variable goes out of scope, but if you do it will always be there and always return the same value when you read from memory. Thanks for replying, I am not trying to be argumentative here, just stating what I thought it meant and why I am confused. I'll be doing some more reading of the D spec to better understand immutability. Cheers, Norm
Apr 10 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/10/22 20:05, norm wrote:
 On Sunday, 10 April 2022 at 23:19:47 UTC, rikki cattermole wrote:
 In my mind immutable data
 means the data will not change and neither will the result of reading
 that data, ever.
Yes.
 I don't get how you can have thread safety guarantees based on immutable
 if reading that data in a thread suddenly becomes undefined behaviour
 and could return anything.
Yes, it would be a bug to attempt to read data that is not live anymore.
 That isn't immutable then.
The lifetime of immutable data can start and end. import std.stdio; struct S { int i; this(int i) { this.i = i; writeln(i, " is (about to be) alive!"); } ~this() { writeln(i, " is (already) dead."); } } void main() { foreach (i; 0 .. 3) { immutable s = S(i); } } The output: 0 is (about to be) alive! 0 is (already) dead. 1 is (about to be) alive! 1 is (already) dead. 2 is (about to be) alive! 2 is (already) dead. Module-level immutable and 'static const' would live much longer but they have a lifetime as well. However, today, the destructor cannot be executed on an immutable object, so I remove the qualifiers with cast(), which sohuld be undefined behavior (today?). immutable(S) moduleS; shared static this() { moduleS = S(42); } shared static ~this() { destroy(cast()moduleS); } void main() { }
 Once instantiated
 immutable data persists for the remainder of the program.
That seems to be the misunderstanding. Again, I think module-level 'immutable' and 'static const' data fits that description.
 You may not
 have access if the variable goes out of scope, but if you do it will
 always be there and always return the same value when you read from 
memory. That description fits D's GC-owned data (including immutables). The lifetime ends when there is no reference to it. Another example is immutable messages passed between threads with std.concurrency: That kind of data clearly originates at run time and the receiving end keeps the data alive as long as it needs. Ali
Apr 10 2022
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Monday, 11 April 2022 at 03:24:11 UTC, Ali Çehreli wrote:
 
 The output:

 0 is (about to be) alive!
 0 is (already) dead.
 1 is (about to be) alive!
 1 is (already) dead.
 2 is (about to be) alive!
 2 is (already) dead.
It worked for me in a different way.
 1 is (about to be) alive!
2 is (already) dead. 2 is (already) dead. 2 is (already) dead.
 2 is (already) dead.
2 is (already) dead. Hello D! 1 is (already) dead. Because I changed the code like this. ```d struct S { . . . string toString() { return ""; } } //S test(inout S s)/* S test(S s)//*/ { s.i = 2; return s; } void main() { immutable s = S(1); test(s).writeln; "Hello".writefln!"%s D!"; } ``` If the inout is set, it does not allow compilation. Thanks, SDB79
Apr 11 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 11 April 2022 at 12:12:39 UTC, Salih Dincer wrote:
 It worked for me in a different way.

 1 is (about to be) alive!
2 is (already) dead. 2 is (already) dead. 2 is (already) dead.
 2 is (already) dead.
2 is (already) dead. Hello D! 1 is (already) dead. Because I changed the code like this. ```d struct S { . . . string toString() { return ""; } } //S test(inout S s)/* S test(S s)//*/ { s.i = 2; return s; } void main() { immutable s = S(1); test(s).writeln; "Hello".writefln!"%s D!"; } ``` If the inout is set, it does not allow compilation.
Because `S` does not contain any pointers or references, you are allowed to create a mutable *copy* of an `S` from an `immutable` source. That's what happens when you pass it to `test`. The extra destructor calls come from the copies made by `test` and `writeln`. If you add a copy constructor, you can see this happening in the output: ```d struct S { /* ... */ this(ref inout typeof(this) other) inout { this.i = other.i; writeln(i, " was copied."); } } ``` ``` 1 is (about to be) alive! 1 was copied. 2 was copied. 2 is (already) dead. 2 was copied. 2 was copied. 2 was copied. 2 is (already) dead. 2 is (already) dead. 2 is (already) dead. 2 is (already) dead. Hello D! 1 is (already) dead. ``` 1 constructor call + 5 copies = 6 destructor calls.
Apr 11 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/11/22 08:02, Paul Backus wrote:

 any pointers or references
To add, Salih and I were in an earlier discussion where that concept appeared as "indirections." Ali
Apr 11 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Monday, 11 April 2022 at 21:48:56 UTC, Ali Çehreli wrote:
 On 4/11/22 08:02, Paul Backus wrote:

 any pointers or references
To add, Salih and I were in an earlier discussion where that concept appeared as "indirections." Ali
I tried the following and I didn't understand one thing: Why is there no need to use dup when slicing? ```d struct S(T) { T fileName; this(T fileName) { this.fileName = fileName; report(); } ~this() { report(); } void report(string func = __FUNCTION__) { import std.stdio; writefln!"%s\nworking with %s"(func, fileName); } } alias T1 = const(char)[]; alias T2 = const char[]; alias T3 = const(char[]); // T3 == T2 alias T4 = immutable char[]; // Not compiling! void main() { auto fileName = "foo.txt".dup; auto s = S!T1(fileName); fileName[0..3] = "bar"; }/* Results: * *T1: * source.S!(const(char)[]).S.this working with foo.txt. source.S!(const(char)[]).S.~this working with bar.txt. * *T2: * source.S!(const(char[])).S.this working with foo.txt source.S!(const(char[])).S.~this working with bar.txt * *T3: * source.S!(const(char[])).S.this working with foo.txt source.S!(const(char[])).S.~this working with bar.txt * *T4 * Error: slice `fileName[0..3]` is not mutable */ ``` SDB 79
Apr 12 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/12/22 21:34, Salih Dincer wrote:

 I tried the following and I didn't understand one thing: Why is there no
 need to use dup when slicing?
I don't think I understand you fully.
 ```d
 struct S(T) {
   T fileName;

    this(T fileName) {
      this.fileName = fileName;
      report();
    }

    ~this() { report(); }

    void report(string func = __FUNCTION__) {
      import std.stdio;
      writefln!"%s\nworking with %s"(func, fileName);
    }
 }
 alias T1 = const(char)[];
 alias T2 = const char[];
 alias T3 = const(char[]); // T3 == T2
 alias T4 = immutable char[]; // Not compiling!


 void main() {
    auto fileName = "foo.txt".dup;
In every test case that 'fileName' is char[].
    auto s = S!T1(fileName);
    fileName[0..3] = "bar";
For that reason, that expression will always succeed. And that's the point I tried to make: Without immutable, struct S *must* make a copy in order to guarantee that its member will remain the same.
 }/* Results:
[...]
    *
    *T4
    *
    Error: slice `fileName[0..3]` is not mutable
Your code was different because I get an error that I expect: The following line fails. auto s = S!T4(fileName); cannot pass argument `fileName` of type `char[]` to parameter `immutable(string) fileName` Exactly! struct S wants immutable (to guarantee that nobody will mutate it) but 'fileName' in main is not immutable. You can make it pass in the T4 case a) by making an immutable copy with .idup: auto s = S!T4(fileName.idup); b) by converting 'fileName' to immutable without a copy: import std.exception; auto s = S!T4(fileName.assumeUnique); // But then, the following will fail. Awesome! // fileName[0..3] = "bar"; I am amazed everyday how great D is; such a fun engineering tool! Ali
Apr 13 2022
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Apr 13, 2022 at 08:39:17AM -0700, Ali Çehreli via Digitalmars-d-learn
wrote:
 On 4/12/22 21:34, Salih Dincer wrote:
 
 I tried the following and I didn't understand one thing: Why is
 there no need to use dup when slicing?
[...] Because of two things: (1) there is a GC, and (2) characters in a string are immutable. Without (1), you will end up with either a memory leak or a dangling pointer; without (2), your slice may randomly mutate when you don't expect it to. T -- May you live all the days of your life. -- Jonathan Swift
Apr 13 2022
prev sibling parent reply wjoe <invalid example.com> writes:
On Monday, 11 April 2022 at 03:24:11 UTC, Ali Çehreli wrote:
 On 4/10/22 20:05, norm wrote:
 On Sunday, 10 April 2022 at 23:19:47 UTC, rikki cattermole
wrote:
 In my mind immutable data
 means the data will not change and neither will the result of
reading
 that data, ever.
Yes.
 I don't get how you can have thread safety guarantees based
on immutable
 if reading that data in a thread suddenly becomes undefined
behaviour
 and could return anything.
Yes, it would be a bug to attempt to read data that is not live anymore.
 That isn't immutable then.
The lifetime of immutable data can start and end. import std.stdio; struct S { int i; this(int i) { this.i = i; writeln(i, " is (about to be) alive!"); } ~this() { writeln(i, " is (already) dead."); } } void main() { foreach (i; 0 .. 3) { immutable s = S(i); } } The output: 0 is (about to be) alive! 0 is (already) dead. 1 is (about to be) alive! 1 is (already) dead. 2 is (about to be) alive! 2 is (already) dead. Module-level immutable and 'static const' would live much longer but they have a lifetime as well. However, today, the destructor cannot be executed on an immutable object, so I remove the qualifiers with cast(), which sohuld be undefined behavior (today?). immutable(S) moduleS; shared static this() { moduleS = S(42); } shared static ~this() { destroy(cast()moduleS); } void main() { }
 Once instantiated
 immutable data persists for the remainder of the program.
That seems to be the misunderstanding. Again, I think module-level 'immutable' and 'static const' data fits that description.
 You may not
 have access if the variable goes out of scope, but if you do
it will
 always be there and always return the same value when you
read from memory. That description fits D's GC-owned data (including immutables). The lifetime ends when there is no reference to it. Another example is immutable messages passed between threads with std.concurrency: That kind of data clearly originates at run time and the receiving end keeps the data alive as long as it needs. Ali
To my understanding immutable data should reside in a separate data segment which itself could reside in ROM. So when the variable, or 'pointer' to the data, goes out of scope just the 'pointer' is gone, the actual data unreachable, but still there. Due to its immutable nature immutable data can't be changed and this, to my understanding, includes deallocation. And because the data could be in ROM any modification is an error. How would you deallocate ROM anyways? Your foreach could be unrolled at compile time, however it could easily be changed to a runtime only loop but this entire concept of creating immutable data on the fly doesn't make sense to me - that should be const's domain. Strings, I know. But the way things are, I hardly see a difference between immutable and const.
Apr 11 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/11/22 05:57, wjoe wrote:

 To my understanding immutable data should reside in a separate data
 segment which itself could reside in ROM.
We are getting into implementation details which a programming language acts as not to care (but has to do especially when it's a system programming language like D. :) ).
 So when the variable, or 'pointer' to the data, goes out of scope just
 the 'pointer' is gone, the actual data unreachable, but still there.
I think it translates to the destructor never being executed. Otherwise, nobody would even know whether the memory location is reused for other pursposes later on.
 Due to its immutable nature immutable data can't be changed and this, to
 my understanding, includes deallocation.
D one language where object lifetime is deliberately separate from memory allocation.
 And because the data could be
 in ROM any modification is an error.
Fully agreed. However, how could I initialize such an object then? (You may have meant a read-only memory page instead of ROM.)
 immutable data on the fly doesn't make sense to me - that should be
 const's domain.
Even 'const' cause confusions because it's used in at least two different ways (even e.g. in C++): 1) I will not mutate data through this reference. For example, a parameter that is pointer to const achieves that: void foo (const(int)[] arr); 2) This variable is const: const i = 42; Well, there is the confusion: There is no "reference" in the second case at all! I don't agree with you when you say immutability should be const's domain because const covers item 1 above as well, where there is no immutability of data whatsoever. The data may be perfectly mutable or immutable, where my access will be readonly. Perhaps you are saying the same thing but enters D's immutable: The data is immutable. immutable data on the fly can be very useful because e.g. it makes multithreaded programming trivial in some cases by removing the need for locking.
 Strings, I know. But the way things are, I hardly see a
 difference between immutable and const.
Imagine a File struct that holds on to a file name. In C++, we would have to make a copy of the constructor argument for two reasons: 1) Lifetime of the object might be short. This is not a problem in D because of the GC. 2) The data might change after I start holding on to it through a reference. This is not a problem *only if* data were immutable because my const parameter cannot preclude the producer from mutating it further. Example: import std.stdio; import std.format; struct S { const(char)[] fileName; this(const(char)[] fileName) { this.fileName = fileName; report(); } ~this() { report(); } void report(string func = __FUNCTION__) { writefln!"%s working with %s."(func, fileName); } } void main() { char[] fileName = "foo.txt".dup; auto s = S(fileName); fileName[0..3] = "bar"; } The output shows that the file name changed between construction and destruction: deneme.S.this working with foo.txt. deneme.S.~this working with bar.txt. If fileName were immutable, then the owner would not be able to mutate anyway, so the struct could get away without copying the file name. Ali
Apr 11 2022
next sibling parent reply Dom DiSc <dominikus scherkl.de> writes:
On Monday, 11 April 2022 at 22:10:07 UTC, Ali Çehreli wrote:
 1) I will not mutate data through this reference. For example, 
 a parameter that is pointer to const achieves that:

   void foo (const(int)[] arr);

 2) This variable is const:

   const i = 42;

 Well, there is the confusion: There is no "reference" in the 
 second case at all!
I think this second case should not be allowed. Use immutable i = 42; instead. The meaning is identical, but we could remove the burden of two different meanings from const if it is not allowed. const should only be allowed in function declarations. A variable must be declared either mutable or immutable. It's only functions that may guarantee not to modify a parameter or the objects they belong to, and so are allowed to work on both mutable and immutable objects.
Apr 12 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/12/22 03:28, Dom DiSc wrote:
 On Monday, 11 April 2022 at 22:10:07 UTC, Ali Çehreli wrote:
 1) I will not mutate data through this reference. For example, a
 parameter that is pointer to const achieves that:

   void foo (const(int)[] arr);

 2) This variable is const:

   const i = 42;

 Well, there is the confusion: There is no "reference" in the second
 case at all!
I think this second case should not be allowed. Use immutable i = 42; instead. The meaning is identical,
Only if there are no indirections as in 'int'. If I use a struct struct S { const(char[]) arr; } void foo(const(char[]) arr) { immutable s = immutable(S)(arr); // <-- ERROR: // Error: cannot implicitly convert expression `arr` of type `const(char[])` to `immutable(string)` } void main() { int[] arr; foo(arr); } The reason is a property of immutable that I like to describe as "selective" (or "demanding"). The struct variable cannot be immutable because it would be demanding that its constructor argument be immutable, which cannot be because foo's 'const' parameter hides that information even when it were immutable e.g. in main().
 but we could remove the burden of two
 different meanings from const if it is not allowed. const should only be
 allowed in function declarations. A variable must be declared either
 mutable or immutable. It's only functions that may guarantee not to
 modify a parameter or the objects they belong to, and so are allowed to
 work on both mutable and immutable objects.
I understand but the guarantee of immutable seems to leave no other choice. Ali
Apr 12 2022
prev sibling parent reply wjoe <invalid example.com> writes:
On Monday, 11 April 2022 at 22:10:07 UTC, Ali Çehreli wrote:
 On 4/11/22 05:57, wjoe wrote:

 And because the data could be
 in ROM any modification is an error.
Fully agreed. However, how could I initialize such an object then? (You may have meant a read-only memory page instead of ROM.)
I was thinking during compile time. By initializing a variable with immutable data or a pointer that points to an address e.G. an EEPROM.
 Even 'const' cause confusions because it's used in at least two 
 different ways (even e.g. in C++):

 1) I will not mutate data through this reference. For example, 
 a parameter that is pointer to const achieves that:

   void foo (const(int)[] arr);

 2) This variable is const:

   const i = 42;

 Well, there is the confusion: There is no "reference" in the 
 second case at all!
In general, I guess, it's a bad idea to reuse the same word for 2 or more distinctly different ideas. Maybe the const keyword in 1) should have a better name. Especially since it's only a promise and the compiler accepts this: void foo (const(char)[] arr) { cast(char[])arr[0..3] = "baz"; } string bar = "123"; foo(bar); assert(bar=="baz"); But I could cast away const and modify the string bar. So with that said
 I don't agree with you when you say immutability should be 
 const's domain because const covers item 1 above as well, where 
 there is no immutability of data whatsoever. The data may be 
 perfectly mutable or immutable, where my access will be 
 readonly.
When I said immutability should be the domain of const I am referring only to 2). I.e. immutable is constant data which is created at compile time - like laws of physics, const as in 2) is constant data which is created at run time - like man made laws, and 1) should get a better name - maybe 'in' and get rid of const. And to be able to use immutable anywhere other than where immutable is explicitly specified, a copy is necessary. I know it's not as simple as that. But one can dream, right? :)
 producer from mutating it further. Example:

 import std.stdio;
 import std.format;

 struct S {
   const(char)[] fileName;

   this(const(char)[] fileName) {
     this.fileName = fileName;
     report();
   }

   ~this() {
     report();
   }

   void report(string func = __FUNCTION__) {
     writefln!"%s working with %s."(func, fileName);
   }
 }

 void main() {
   char[] fileName = "foo.txt".dup;
   auto s = S(fileName);
   fileName[0..3] = "bar";
 }


 If fileName were immutable, then the owner would not be able to 
 mutate anyway, so the struct could get away without copying the 
 file name.

 Ali
I presume you refer to fileName in main() ? And if yes, if it were const, it couldn't be mutated either, so isn't immutable and const sort of synonymous in that case or am I missing your point?
Apr 12 2022
next sibling parent reply ag0aep6g <anonymous example.com> writes:
On Tuesday, 12 April 2022 at 19:54:13 UTC, wjoe wrote:
 Especially since it's only a promise and the compiler accepts 
 this:

 void foo (const(char)[] arr)
 {
   cast(char[])arr[0..3] = "baz";
 }
 string bar = "123";
 foo(bar);
 assert(bar=="baz");

 But I could cast away const and modify the string bar.
No, you could not. You're relying on undefined behavior there. Just because the compiler accepts something, doesn't mean it's ok. If you want to be guarded against wandering into undefined territory, that's what safe does. With safe, the cast doesn't compile.
Apr 12 2022
parent reply wjoe <invalid example.com> writes:
On Tuesday, 12 April 2022 at 22:23:18 UTC, ag0aep6g wrote:
 On Tuesday, 12 April 2022 at 19:54:13 UTC, wjoe wrote:
 Especially since it's only a promise and the compiler accepts 
 this:

 void foo (const(char)[] arr)
 {
   cast(char[])arr[0..3] = "baz";
 }
 string bar = "123";
 foo(bar);
 assert(bar=="baz");

 But I could cast away const and modify the string bar.
No, you could not. You're relying on undefined behavior there. Just because the compiler accepts something, doesn't mean it's ok. If you want to be guarded against wandering into undefined territory, that's what safe does. With safe, the cast doesn't compile.
Undefined behavior yes, but regardless the example proves it can be done in system code. A few versions ago, possibly due to a bug or regression, the compiler didn't complain in safe code either. Of course you are correct academically. However, since it's possible, I'd wager my last hat that code like this is out in the wild.
Apr 14 2022
parent reply ag0aep6g <anonymous example.com> writes:
On 14.04.22 13:42, wjoe wrote:
 Undefined behavior yes, but regardless the example proves it can be done 
 in  system code.
 A few versions ago, possibly due to a bug or regression, the compiler 
 didn't complain in  safe code either.
 
 Of course you are correct academically. However, since it's possible, 
 I'd wager my last hat that code like this is out in the wild.
No, it cannot be done in system code. The example only proves that you can write nonsense code that has no defined meaning. Note that the nonsense you wrote behaves as you describe only in Windows. Elsewhere, you get a segfault. But it doesn't matter how the executable actually behaves. You cannot cite the result of undefined behavior when arguing language semantics.
Apr 14 2022
parent reply wjoe <invalid example.com> writes:
On Thursday, 14 April 2022 at 12:10:04 UTC, ag0aep6g wrote:
 On 14.04.22 13:42, wjoe wrote:
 Undefined behavior yes, but regardless the example proves it 
 can be done in  system code.
 A few versions ago, possibly due to a bug or regression, the 
 compiler didn't complain in  safe code either.
 
 Of course you are correct academically. However, since it's 
 possible, I'd wager my last hat that code like this is out in 
 the wild.
No, it cannot be done in system code. The example only proves that you can write nonsense code that has no defined meaning. Note that the nonsense you wrote behaves as you describe only in Windows. Elsewhere, you get a segfault. But it doesn't matter how the executable actually behaves. You cannot cite the result of undefined behavior when arguing language semantics.
Well I'm not using Windows so I wouldn't know but I compiled an ran that program on Linux and it didn't segfault. If it had I wouldn't have included that part in my reply. On the matter of undefined behavior. Technically a program is in undefined behavior land after throwing an error, thus every unittest that continues after assertThrown is therefore nonsense code, is it not ?
Apr 17 2022
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Apr 17, 2022 at 01:06:36PM +0000, wjoe via Digitalmars-d-learn wrote:
[...]
 On the matter of undefined behavior. Technically a program is in
 undefined behavior land after throwing an error, thus every unittest
 that continues after assertThrown is therefore nonsense code, is it
 not ?
The spec explicitly makes an exception(!) in this case. See 10.24.11.3: https://dlang.org/spec/expression.html#assert_expressions as well as 27.3: https://dlang.org/spec/unittest.html T -- An elephant: A mouse built to government specifications. -- Robert Heinlein
Apr 17 2022
parent ag0aep6g <anonymous example.com> writes:
On 17.04.22 15:27, H. S. Teoh wrote:
 On Sun, Apr 17, 2022 at 01:06:36PM +0000, wjoe via Digitalmars-d-learn wrote:
 [...]
 On the matter of undefined behavior. Technically a program is in
 undefined behavior land after throwing an error, thus every unittest
 that continues after assertThrown is therefore nonsense code, is it
 not ?
The spec explicitly makes an exception(!) in this case. See 10.24.11.3: https://dlang.org/spec/expression.html#assert_expressions as well as 27.3: https://dlang.org/spec/unittest.html
But the failing assert is not part of the unittest directly. It's in a function that is called from the unittest. It takes a generous interpretation of the spec to include such asserts in the exception. Letting failing asserts cause UB is a mistake anyway. It stems from a misguided desire to re-use asserts for optimization, when nobody actually uses them that way.
Apr 17 2022
prev sibling parent reply ag0aep6g <anonymous example.com> writes:
On 17.04.22 15:06, wjoe wrote:
 On the matter of undefined behavior. Technically a program is in 
 undefined behavior land after throwing an error, thus every unittest 
 that continues after assertThrown is therefore nonsense code, is it not ?
Yes. Failing asserts are a messy part of the language. They are supposed to be: 1) not catchable, because they indicate a bug in the program; 2) catchable in order to be testable; 3) assumed impossible for optimization purposes. Those goals are at odds with each other, and I don't think the spec manages to consolidate them. But mutating `immutable` data is not messy. It's simply not allowed.
Apr 17 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Apr 17, 2022 at 04:09:12PM +0200, ag0aep6g via Digitalmars-d-learn
wrote:
[...]
 Failing asserts are a messy part of the language. They are supposed to
 be:
 
 1) not catchable, because they indicate a bug in the program;
 2) catchable in order to be testable;
 3) assumed impossible for optimization purposes.
 
 Those goals are at odds with each other, and I don't think the spec
 manages to consolidate them.
Not entirely true. See paragraph 3 in: https://dlang.org/spec/unittest.html and 10.24.11.3 in: https://dlang.org/spec/expression.html#assert_expressions T -- Latin's a dead language, as dead as can be; it killed off all the Romans, and now it's killing me! -- Schoolboy
Apr 17 2022
parent reply wjoe <invalid example.com> writes:
On Sunday, 17 April 2022 at 14:14:37 UTC, H. S. Teoh wrote:
 Not entirely true. See paragraph 3 in:

 	https://dlang.org/spec/unittest.html

 and 10.24.11.3 in:

 	https://dlang.org/spec/expression.html#assert_expressions


 T
Thanks. Either I missed that the last time I checked or it wasn't there ;) But the interesting question is: does the compiler go out of its way to make that happen or is it just paragraphs written in the spec?
Apr 18 2022
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Apr 18, 2022 at 12:55:26PM +0000, wjoe via Digitalmars-d-learn wrote:
 On Sunday, 17 April 2022 at 14:14:37 UTC, H. S. Teoh wrote:
 Not entirely true. See paragraph 3 in:
 
 	https://dlang.org/spec/unittest.html
 
 and 10.24.11.3 in:
 
 	https://dlang.org/spec/expression.html#assert_expressions
[...]
 Thanks. Either I missed that the last time I checked or it wasn't
 there ;)
 But the interesting question is: does the compiler go out of its way
 to make that happen or is it just paragraphs written in the spec?
My guess: the spec was amended after the fact. :-D T -- Those who don't understand D are condemned to reinvent it, poorly. -- Daniel N
Apr 18 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/12/22 12:54, wjoe wrote:

 I.e. immutable is constant data which is created at compile time - like
 laws of physics,
For completeness, we already have 'enum' and 'static const' for that.
 should get a better name - maybe 'in' and get rid of const.
Yes! 'in' for parameters! :D
 import std.stdio;
 import std.format;

 struct S {
   const(char)[] fileName;

   this(const(char)[] fileName) {
     this.fileName = fileName;
     report();
   }

   ~this() {
     report();
   }

   void report(string func = __FUNCTION__) {
     writefln!"%s working with %s."(func, fileName);
   }
 }

 void main() {
   char[] fileName = "foo.txt".dup;
   auto s = S(fileName);
   fileName[0..3] = "bar";
 }


 If fileName were immutable, then the owner would not be able to mutate
 anyway, so the struct could get away without copying the file name.

 Ali
I presume you refer to fileName in main() ?
No, I meant the member 'fileName'. If the member is 'const' (as in C++ and as in the example above), you have to take a copy because the caller may mutate. (The example is demonstrating the case where the struct assumes data won't changed and gets surprised by the mutation.)
 And if yes, if it were
 const, it couldn't be mutated either,
You started as assuming 'fileName' in main, so, I will continue with that. If the struct had 'const fileName' and pleaded that the caller also define a 'const' variable, that would be wishful thinking. The struct cannot trust that all callers will obey that plea. That kind of trust (more like guarantee) comes with 'immutable' and this is a good example of how immutable is different from const. (immutable does not exist in some languages like C++.)
 so isn't immutable and const sort
 of synonymous in that case or am I missing your point?
If on the other hand, the member were immutable, the caller would have to provide immutable data and the struct could rely on the fact that the data was immutable forever. No need for a copy... Ali
Apr 12 2022
parent wjoe <invalid example.com> writes:
On Tuesday, 12 April 2022 at 23:23:59 UTC, Ali Çehreli wrote:
 [...]
Looking at this from a technical perspective - everything you say is true - and thanks for clearing up some of my confusion in that department. Looking at this from a natural language (English) perspective - words prompt ideas and expectations. If these expectations aren't met confusion and misunderstanding happens (and that's not just true for software development). Although, perhaps, that's only a problem for (some) non native English speakers ? The fact remains that there's confusion and misunderstandings regarding const, immutable and const 2.
Apr 14 2022
prev sibling parent rikki cattermole <rikki cattermole.co.nz> writes:
Storage classes like immutable/const/shared are not tied to any memory 
management strategy. Nor does it dictate memory lifetime.

It only dictates how it can be interacted with when you have a reference 
to it.
Apr 10 2022
prev sibling parent IGotD- <nise nise.com> writes:
On Sunday, 10 April 2022 at 23:19:47 UTC, rikki cattermole wrote:
 immutable isn't tied to lifetime semantics.

 It only says that this memory will never be modified by anyone 
 during its lifetime.

 Anyway, the real problem is with const. Both mutable and 
 immutable become it automatically.
I was thinking about that, often when using const you use it when passing parameters to functions. This is essentially borrowing. The situation is similar with C++ with unique_ptr and shared_ptr. Often C++ interfaces use const* when using pointers and not their smart pointer counterparts, so essentially the ownership remains, while "borrowing" are using raw pointers. A C++ interface only accepts the smart pointers when you want to change ownership. D could use a similar approach, when using pointers/references you shouldn't alter the internal data including reference count. What I would interested in is if D could have move by default depending on type. In this case the RC pointer wrapper could be move by default. Increasing is only done when calling "clone" (similar to Rust). This way RC increases are optimized naturally. What I don't want from Rust is the runtime aliasing check (RefCell) on at all times. I rather go with that the compiler assumes no aliasing but the programmer is responsible for this. You can have runtime aliasing/borrowing check in debug mode but in release build it can be removed. This is similar to bounds checking where you can choose to have it or not.
Apr 11 2022
prev sibling parent user1234 <user1234 12.de> writes:
On Sunday, 10 April 2022 at 23:05:24 UTC, norm wrote:
 Hi All,

 I am clearly misunderstanding something fundamental, and 
 probably obvious :D

 Reading some of the discussions on __metadata I was wondering 
 if someone could explain why a immutable reference counting 
 type is needed. By definition a reference counter cannot be 
 immutable, so what would be the use case that requires it? It 
 cannot really be pure nor safe either because the ref goes out 
 of scope and the allocation is freed. How is this immutable?


 Thanks,
 Norm
refcounting would require a concept of "tail const" / "tail immutable" so that transitivity of the qualifier does not affect the data used to refcount (basically the field that hold the count) but only the data that **are** refcounted.
Apr 11 2022